How to insert array formula into Excel via VBA? - arrays

Previously I have an Excel VBA code that performs index & match formula from 1 sheet to another for an entire column in a table. Code example as below:
With Me.Range("table1[Description]")
.Formula = "=IFerror(INDEX(table2,MATCH(B4,table2[Asset No],0),2),"""")"
.Value = .Value
End With
Now I would like to add an array function into the sheet so I copied the formula and replace the old formula with array along with other modifications.
Sub refresh()
With Me.Range("table1[Last Service Date]")
.FormulaArray = "=LARGE(IF(table2[[#All],[Asset No]]=[#[Asset No]],table2[[#All],[Entry Date]]),2)"
.Value = .Value
End With
End Sub
But when I try to test the code, I keep getting the error message 400.
Anything I need to change in my code for array formulas?

What's the point of using Me in the code? Have you placed that code in the Sheet Module? I would suggest you to move this code on to the Standard Module like Module1 and replace the Me keyword with the proper sheet reference. Otherwise I hope you have a good reason to place that code into the sheet module.
Also you should place the Array Formula in the first cell of the table in the desired column and since the data is formatted as an Excel Table, the whole column will be filled with the formula automatically. If not, you can then use AutoFill property to fill down the formula down the rows.
Give this a try to see if that resolves your issue.
The answer assumes that the formula you are trying to place through VBA works well when you put it manually on the Sheet.
With Me.Range("table1[Last Service Date]").Cells(1)
.FormulaArray = "=LARGE(IF(table2[[#All],[Asset No]]=[#[Asset No]],table2[[#All],[Entry Date]]),2)"
.Value = .Value
End With

Related

Error when filtering after attempting to create an array in Excel with vba

I'm attempting to take a list of names and filter another list based on whether these names appear. To do so, I want to create an array of the names and then take this array to apply an autofilter to the other sheet from a specific column. Here's the code I currently have:
Dim AdvisorEINs As Variant
AdvisorEINs = Worksheets("Names to Filter").Range("A1:A36").Value
Worksheets("All Advisors").Range("$A$1:$DZ$2216").AutoFilter Field:=15, Criteria1:=AdvisorEINs.Value, Operator:=xlFilterValues
Currently this is returning Run-time error '424': Object required when attempting to run and debugging is highlighting the filtering line. I've added in a Debug.Print on the AdvisorEINs variable, which outputs the same error message and suggests to me that this is due to variable not picking up data correctly. I'm not certain how to have the array pick up the data any other way - is someone able to advise on this?
you have to
use AdvisorEINs instead of AdvisorEINs.Value
transpose the array
, because the Value property of a multicell range returns a 2D array (though with one column only... )
like follows:
Dim AdvisorEINs As Variant
AdvisorEINs = Worksheets("Names to Filter").Range("A1:A36").Value
Worksheets("All Advisors").Range("$A$1:$DZ$2216").AutoFilter Field:=15, Criteria1:=Application.Transpose(AdvisorEINs), Operator:=xlFilterValues

Excel Sumif Array- works in sheet returns false in vba

I have a question regarding the sumif array function in excel.
My current formula works perfectly when typed to a cell and used as a array Ctrl + Shift + Enter and the filled down the column with the double click.
the formula is =SUMIFS(T:T,A:A,A2,C:C,"<"&OFFSET($H$1,MATCH(1,(A:A=A2)*(H:H=[IPE.xlsm]Overview!$C$3),0),-5))
My problem is when I try to integrate this formula into my current vba script.
Below is the section where Im running into this error.
when the code is run it returns a false statement, again though if I copy paste the formula it returns the correct value.
Sub Enter_Array_Formulas()
Range("W2").FormulaArray = "=SUMIFS(T:T,A:A,A2,C:C," < "&
OFFSET($H$1,MATCH (1,(A:A=A2)*(H:H=[IPE.xlsm]Overview!$C$3),0),-5))"
Range("U2").Select
Selection.End(xlDown).Select
ActiveCell.Offset(0, 2).Select
Range(Selection, Selection.End(xlUp)).Offset(0, 0).Select
Selection.FillDown
End Sub
Im guessing its a syntax error but I'm unable to find the hiccup.
Thanks in advance,
Ross
Try this instead:
Range("W2").FormulaArray = "=SUMIFS(T:T,A:A,A2,C:C,""<""&OFFSET($H$1,MATCH(1,(A:A=A2)*(H:H=[IPE.xlsm]Overview!$C$3),0),-5))"
You need to be careful when using speech marks in strings -- use two "" instead
Also, you entered a blank space after MATCH, which would be corrected for you on the worksheet but probably just throws an error in VBA.

MS Excel: "MATCH()" does not find cells containing text if lookup array is too large

I am creating a large and complicated schedule, and I want one view which shows the schedule as a day-time grid, and another which allows one to look up a speaker by name from an alphabetical list. I have posted a simplified example here:
In the alphabetical list, the day and time should be populated by a function using MATCH. Just as an example, I manually typed what I would like to have happen for Jones.
I cannot get MATCH() to locate the speaker's name in the timetable correctly. There are no hidden characters: notice that in cell D15, Excel correctly recognizes that G2 and C7 are identical.
Here is what happens if I put various code in H2:
=MATCH(G2,$A$1:$D$9) results in #N/A
=MATCH(G2,$C$2:$C$9) results in #N/A
=MATCH(G2,$B$7:$D$7) results in 2 (correctly!)
=MATCH(G2,$A$7:$D$7) results in #N/A
What I would like is to put =MATCH(G2,$A$1:$D$9) into H2 and then fill cells down to H25, and have Excel indicate the column number of the day in which the adjacent name appears, then use INDIRECT or something to convert this number into the day of the week.
It may be that including column A in the search array causes problems because of the different data types. As an experiment, I made the first column into TEXT, and in this case =MATCH(G2,$A$7:$D$7) incorrectly returns 1!
And even so, I cannot understand why $B$7:$D$7 works but neither $C$2:$C$9 nor $B$7:$D$8 will.
Any workarounds or alternative strategies would be greatly appreciated, thanks.
To do this you need to add in some other logic to find the correct column and row. This AGGREGATE() Function does the job.
For Day use:
=INDEX($A$1:$D$1,AGGREGATE(15,6,COLUMN($A$2:$D$9)/(($A$2:$D$9=G2)),1))
For Hour:
=INDEX($A$1:$A$9,AGGREGATE(15,6,ROW($B$1:$D$9)/(($B$1:$D$9=G2)),1))
The AGGREGATE() Function was introduced in Excel 2010.
For other Versions:
Pre 2010, they will need to be Array Formulas:
Day:
=INDEX($A$1:$D$1,MIN(IF($A$2:$D$9=G2,COLUMN($A$2:$D$9))))
Hour:
=INDEX($A$1:$A$9,MIN(IF($B$1:$D$9=G2,ROW($B$1:$D$9))))
Being an Array Formula it must be confirmed with Ctrl-Shift-Enter when exiting Edit mode. When done correctly Excel will automatically put {} around the formula to denote an array formula.
Newest Office 360 or online:
Day:
=INDEX($A$1:$D$1,MINIFS(COLUMN($A$2:$D$9),$A$2:$D$9,G2))
Hour:
=INDEX($A$1:$A$9,MINIFS(ROW($B$1:$D$9),$B$1:$D$9,G2))
As to the reason MATCH will not work in this case:
MATCH() only works with a single row or column and not a multiple column/row range. It is set up to return a number equal to the order place found and therefore must be a 1 dimensional array.
The most efficient way to do this given your dataset is to use three MATCH queries - one for each column.
For the Day, that looks like this:
=IF(ISERROR(MATCH(G2,$B$2:$B$10,0)),"",$B$1)&IF(ISERROR(MATCH(G2,$C$2:$C$10,0)),"",$C$1)&IF(ISERROR(MATCH(G2,$D$2:$D$10,0)),"",$D$1)
For the Time, that looks like this:
=INDEX($A$2:$A$10,IFERROR(MATCH(G2,$B$2:$B$10,0),0) + IFERROR(MATCH(G2,$C$2:$C$10,0),0) + IFERROR(MATCH(G2,$D$2:$D$10,0),0))
...but truth be told, on small datasets such as this one, you won't notice any performance difference on this approach vs Scott's AGGREGATE approach. On large datasets (thousands of rows) you probably will.
Note that another reason your initial approach failed is that you did not specify the 3rd argument of MATCH, and so Excel used the default value that assumes your list data is sorted alphabetically. You almost never want to omit that argument, and you almost always want to use FALSE (or Zero, which means FALSE to Excel)
Alternative solution with vba & listobjects (you need to give the two tables the names as appear in the code below)
sheet screenshot
Public Sub makeAppointmentList()
Dim aSheet As Worksheet
Set aSheet = ThisWorkbook.Worksheets("sheet1")
Dim aSchedule As ListObject
Set aSchedule = aSheet.ListObjects("schedule")
Dim anAppointmentList As ListObject
Set anAppointmentList = aSheet.ListObjects("appointmentList")
On Error Resume Next
anAppointmentList.DataBodyRange.Delete
On Error GoTo 0
Dim c As ListColumn
Dim r As ListRow
Dim newRow As ListRow
For Each c In aSchedule.ListColumns
For Each r In aSchedule.ListRows
If c.Index > 1 And Intersect(c.Range, r.Range) <> "" Then
Set newRow = anAppointmentList.ListRows.Add
Intersect(newRow.Range, anAppointmentList.ListColumns("Name").Range).Value = Intersect(c.Range, r.Range)
Intersect(newRow.Range, anAppointmentList.ListColumns("Day").Range).Value = Intersect(c.Range, aSchedule.HeaderRowRange)
Intersect(newRow.Range, anAppointmentList.ListColumns("Time").Range).Value = Intersect(aSchedule.ListColumns(1).Range, r.Range)
End If
Next r
Next c
anAppointmentList.Sort.SortFields.Clear
anAppointmentList.Sort.SortFields.Add Key:=Intersect(anAppointmentList.HeaderRowRange, _
anAppointmentList.ListColumns("Name").Range)
anAppointmentList.Sort.SortFields.Add Key:=Intersect(anAppointmentList.HeaderRowRange, _
anAppointmentList.ListColumns("Day").Range), _
CustomOrder:="Mon,Tue,Wed,Thu,Fri,Sat,Sun"
anAppointmentList.Sort.SortFields.Add Key:=Intersect(anAppointmentList.HeaderRowRange, _
anAppointmentList.ListColumns("Time").Range)
anAppointmentList.Sort.Apply
Dim s As SortField
End Sub

How to select 2-dimensional ranges using variables and not "A1"-format?

I realize this must be a really basic question but I can't seem to get this right. This last month of trying to learn VBA always sees me stuck on problems relating to this. I have searched for the answer but still struggle. Some help would be appreciated!
So, what I want to do is to select and manipulate ranges based on their numeric order, like row1,col1 to row 15,col7. Instead of "A1:G15".
For instance, the following code should format the copy of a pivot table:
Sub layout()
Dim searchterm As String: searchterm = "Grand Total"
rad = RowIndexer(searchterm) 'finds location of last row
kolumn = ColIndexer(searchterm) 'finds location of last column
ActiveSheet.Range(Cells(15, 1), Cells(rad, kolumn)).Style = "SAPBEXfilterItem"
End Sub
I have also tried converting the range to "A1"-style, to no avail:
Start = Cells(counter, 1).Address
Finish = Cells(counter, kolumn).Address
Range("Start:Stop").Style = "SAPBEXfilterItem"
This question is very generic though so don't focus too much on the actual application. Just tell me how to work with ranges when you usually have just indices :)
In both cases I'm only able to select the first column and not the entire range. I heard someone mention that VBA is not "matrix based" and a lot of code I look at seems to overuse loops. Is the problem actually that you can only manipulate one one-dimensional array at a time? That would be really annoying...
You can indeed select ranges by using Range and using Cells(row, column). The correct usage is as follows, to select A1:G15
Dim wS as Worksheet
Set wS = ActiveSheet
Range(wS.cells(1,1), wS.cells(15,7)).select
Note that I did specify which sheet I am using INSIDE the Range method and applied to the cells object. That's the proper way to do it.

VBA Excel vlookup in loop problem

I have a problem with my VBA code in an excel spreadsheet containing orders. Each row contains a customer number, which I use to look up the customer email address, contained in a different sheet in the workbook.
The vlookup code works fine for a single cell, but the problem is when I try to loop through all of the rows of the spreadsheet. The Excel formula for a single cell is, e.g.,
=VLOOKUP(B2,Customers!A2:D1000,4,FALSE)
The VBA code generated for this is:
Range("M2").Select
ActiveCell.FormulaR1C1 = _
"=VLOOKUP(RC[-11],Customers!RC[-12]:R[999]C[-9],4,FALSE)"
Incorporating this into a loop, after selecting the starting cell, I have the following:
Cells(2, 13).Select
Do
ActiveCell.FormulaR1C1 = "=VLOOKUP(RC[-11],Customers!RC[-12]:R[999]C[-9],4,FALSE)"
ActiveCell.Offset(1, 0).Select
Loop Until IsEmpty(ActiveCell.Offset(0, -10))
The problem is that I want the "table array" to be fixed, not relative to the cell whose value is being looked up. But I absolutely can't figure out how to do it. If I change the code as follows, I get a run-time error:
ActiveCell.FormulaR1C1 = _
"=VLOOKUP(RC[-11],Customers!A2:D1000,4,FALSE)"
I have tried quoting, unquoting, setting a range variable, using the range variable with .address... can someone please help?
Thank you so much.
I'm pretty sure the brackets in your R1C1 formula indicate that you are specifying a relative range (especially with the negatives in them). If you want to specify an absolute range, you need to specify the R1C1 cells without brackets; e.g. R2C2:R4C4.
As a simple example:
Sub test()
Sheet1.Range("C5").FormulaR1C1 = "=VLOOKUP(1,R1C1:R3C3,2,FALSE)"
End Sub
Running this gives you an absolute "A1"-style formula in cell C5.
I think your problem might be:
Customers!RC[-12]:R[999]C[-9]
Because it is a relative range. You have to explicitly specify where your data table is; e.g. Customers!RC12:R999C9 (you need to figure out where it is on your sheet).
An easy way of figuring this out is highlighting your data table on your worksheet, then switch to the Visual Basic Editor and manually run this (put your cursor inside of the Sub, and press the 'Play' button or go to Run->Run).
Sub test2()
Dim r As Range
Set r = Application.Selection
InputBox "your r1c1 range:", , r.Address(True, True, xlR1C1)
End Sub

Resources