INDEX and AGGREGATE Function in Excel VBA - excel
I'm trying to move an in cell formula to VBA, because otherwise it's always recalculating, even when I deactivate the excel option, it comes back when I reopen the file. That's why I want to move that formula to VBA, where it happens only when I press a button, which is much smarter.
I have a master table with data, which I aggregate and index and express it on another sheet in a table. -> column A to S are in the master table, in the aggregated table, I will only have column A,C,E,G,H,I,J,K,L,M and P
The formula I want to move to VBA is the following:
=IFERROR(INDEX(Endkontrolle!$A:$S;AGGREGATE(15;6;ROW(Endkontrolle!$A:$S)/((FIND($B$3;Endkontrolle!$F:$F;1)>0)*(Endkontrolle!$S:$S="x"));ROW()-32)-0;1);"")
Can somebody help me translate that formula to VBA script?
thank you very much
Try this code:
Sub Button1_Click2()
'Declarations.
Dim RngTable As Range
Dim RngTarget As Range
Dim StrColumnsIndex As String
'A string is used to stores the index of the columns to be copied.
StrColumnsIndex = "1;3;5;7;8;9;10;11;14;15;16"
'RngTable is set as the range that will host the aggregated table.
Set RngTable = Sheets("Aggregated sheet").Range("A33:K34") '< EDIT THIS LINE ACCORDGLY TO YOU NEED
'Clearing RngTable.
RngTable.ClearContents
'Checking if StrColumnsIndex and RngTable are compatible.
If UBound(Split(StrColumnsIndex, ";")) + 1 <> RngTable.Columns.Count Then
MsgBox "The number of columns requested via StrColumnsIndex and the number of columns avaiable in RngTable do not match. Redefine the variables properly. The aggregated table will not be updated.", vbCritical + vbOKOnly, "Variable mismatch"
Exit Sub
End If
'Covering each cell in RngTable.
For Each RngTarget In RngTable
'The result is reported in each cell. The [row] element of the INDEX is obtained by subtracting _
RngTable.Row from the RngTarget.Row and adding one. This way each row is properly reported. The _
[col] element of the INDEX is obrained by splitting StrColumnsIndex using the difference between _
the RngTarget.Column and RngTable.Column as index. This way each requested column as listed in _
StrColumnsIndex is reported.
'RngTarget.Formula = "=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x""))," & RngTarget.Row - RngTable.Row + 1 & ")-0," & Split(StrColumnsIndex, ";")(RngTarget.Column - RngTable.Column) * 1 & "),"""")"
RngTarget.Value = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x""))," & RngTarget.Row - RngTable.Row + 1 & ")-0," & Split(StrColumnsIndex, ";")(RngTarget.Column - RngTable.Column) * 1 & "),"""")")
'If RngTarget contains nothing then it's assumed there are no more results to be reported and the macro is terminated.
If RngTarget.Value = "" Then Exit Sub
Next
End Sub
Thanks for that. I implemented it and it works for 1 row. If I want to add the next data set from the main table, that does only repeat the content from previous row. How can I achieve, that it lists me more than 1 line of aggregated data?
Expected result:
it picks the relevant rows of data and lists it (different data according the find criteria)
Actual result:
it picks only 1 row and repeats it for the second line
Now I defined following code:
Sub Button1_Click()
Cells(33, 1) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,1),"""")")
Cells(33, 2) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,3),"""")")
Cells(33, 3) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,5),"""")")
Cells(33, 4) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,7),"""")")
Cells(33, 5) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,8),"""")")
Cells(33, 6) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,9),"""")")
Cells(33, 7) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,10),"""")")
Cells(33, 8) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,11),"""")")
Cells(33, 9) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,14),"""")")
Cells(33, 10) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,15),"""")")
Cells(33, 11) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,16),"""")")
Cells(34, 1) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,1),"""")")
Cells(34, 2) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,3),"""")")
Cells(34, 3) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,5),"""")")
Cells(34, 4) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,7),"""")")
Cells(34, 5) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,8),"""")")
Cells(34, 6) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,9),"""")")
Cells(34, 7) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,10),"""")")
Cells(34, 8) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,11),"""")")
Cells(34, 9) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,14),"""")")
Cells(34, 10) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,15),"""")")
Cells(34, 11) = Evaluate("=IFERROR(INDEX(Endkontrolle!A:S,AGGREGATE(15,6,ROW(Endkontrolle!A:S)/((FIND(B3,Endkontrolle!F:F,1)>0)*(Endkontrolle!S:S=""x"")),ROW()-32)-0,16),"""")")
End Sub
Here's a sample of the data in the main table "Endkontrolle":
Date
Product
Employee
...
Date Range
22.04.2022
MOTI
AKAH
...
x
23.04.2022
MOTI_BG
AKAH
...
x
26.04.2022
MOTI
AKAH
...
On the reporting page, I would like to list down up to 20 rows of Data, which are in the Date Range ('x') from the "Endkontrolle" worksheet.
In the upper example, it should list row 1+2, but not 3.
Related
PivotFilters.add no longer working in my spreadsheet
This code has always worked in the past. Nothing has changed in the workbook and I have verified that all referenced data is formatted correctly. Any idea what would have "broken" this? It no longer applies the "between" filter to the dates in the pivot tables. Verified pivot tables work when manually altered. Checked fields in data table to make sure nothing has changed and there is no corrupt data entered. StartDate = Sheet17.Range("D5").Value EndDate = Sheet17.Range("D6").Value Set pt = Sheet6.PivotTables(1) Set ptF1 = pt.PivotFields("Est closing Date") ptF1.ClearAllFilters ptF1.PivotFilters.Add _ Type:=xlDateBetween, Value1:=StartDate, Value2:=EndDate Sheet17.Range("M10:Q999").ClearContents pt.TableRange1.Offset(4, 0).Copy Sheet17.Cells(10, 13).PasteSpecial xlPasteValues Set pt = Sheet6.PivotTables(2) Set ptF1 = pt.PivotFields("Funds Date") ptF1.ClearAllFilters ptF1.PivotFilters.Add _ Type:=xlDateBetween, Value1:=StartDate, Value2:=EndDate Sheet17.Range("A10:E999").ClearContents 'omit copying the top row of the PivotTable: pt.TableRange2.Offset(4, 0).Copy Sheet17.Cells(10, 1).PasteSpecial xlPasteValues 'Fill in dates For ii = 11 To 100 If (Me.Cells(ii, 1) = "" And Me.Cells(ii, 2) <> "") Then Me.Cells(ii, 1) = Me.Cells(ii - 1, 1) End If Next 'Fill in dates For ii = 11 To 100 If (Me.Cells(ii, 8) = "" And Me.Cells(ii, 9) <> "") Then Me.Cells(ii, 8) = Me.Cells(ii - 1, 8) End If Next End Sub
I guess it's about date formatting. Please try to ensure, that the dates are formatted correctly, e. g. by this: ptF1.PivotFilters.Add2 _ Type:=xlDateBetween, _ Value1:=CStr(CDate(StartDate)), Value2:=CStr(CDate(EndDate)), _ WholeDayFilter:=True
If variable range is > 3 than do action else do other action
Some background: Each month I build a pivot table that has approx 30 or so business units (along the y axis) - lets call them groups. Each group has a number of GL accounts that change month to month. For example, Group 14 might have 10 GL accounts one month than the next have only 3. For each group, we need the summation of the totals for the GL accounts (that start with PL203000 & PL211010) for each group. Before we had to total these GL accounts for each group by hand. This has been solved with the code I have displayed below. The code works perfectly when each group has more than one GL account (See pic 1) The problem I am facing is when there is only one GL account, the code doesn't sum the correct amounts (see 2nd pic). When digging into my code, you can see that it is summing the incorrect sections since i have a Rows.Count.End(xlUp) establishing the range. If there is only one GL account, it skips to the next section thereby establishing an incorrect formula Perhaps my code needs to be completely revamped in order to account for groups where there is only one GL account to sum? If so, what sort of if statement can i code where it ignores groups that have only one GL account? If not, than is the solution to have VBA count the range and if it is less than 3, ignore group and move on to the next? 'this section spits out the values needed to sum For i = nRowMax To 4 Step -1 If Left(Cells(i, 1), 8) = "PL211010" Or Left(Cells(i, 1), 8) = "PL203000" Then Cells(i, 4).Copy Cells(i, 5).PasteSpecial xlPasteValues Range(Cells(i, 1), Cells(i, 4)).Select With Selection.Interior .Pattern = xlSolid .PatternColorIndex = xlAutomatic .Color = 65535 .TintAndShade = 0 .PatternTintAndShade = 0 End With End If Next i Application.CutCopyMode = False 'this section uses the values the first section specified to write the sum formula 'i believe the macro uses this section of code to write the first formula and the next section of code writes the formulas for the rest of the groups Dim firstRow As Variant Dim finalRow As Variant finalRow = Range("E" & Rows.Count).End(xlUp).Row firstRow = Cells(finalRow, 5).End(xlUp).Row If IsNumeric(Cells(finalRow + 1, 5)) Then Cells(firstRow, 6).Formula = "=SUM(D" & firstRow & ":D" & finalRow & ")" End If 'this section goes through the whole sheet to sum each group For y = firstRow To 5 Step -1 finalRow = Cells(y, 5).End(xlUp).Row firstRow = Cells(finalRow, 5).End(xlUp).Row If firstRow < 5 Then firstRow = 5 If IsNumeric(Cells(finalRow + 1, 5)) Then Cells(firstRow, 6).Formula = "=SUM(D" & firstRow & ":D" & finalRow &")" End If y = firstRow 'If firstRow = 5 Then Exit Sub Next y
If your dataset is an accurate enough example, you can scan through your business units and pick out only what you need. I have some example code here that will build up your sum range by using the Union function and applying that to the SUM formula when the entire business unit has been scanned. Of course, this is only an example that fits the data shown. You'll have to expand it to fit situations that are not visible to me. To simplify the logic, I've separated the code into a function that will start scanning rows for a business unit and will stop when it reaches the end of the business unit -- the test I'm using for detecting the start of the next BU is a line that does not start with "PL". This may or may not be correct for all your data. Because this code is checking each line and accumulating the sum range using the Union, if you only have one cell, you'll still get a formula that says =SUM($D$30) but it works. Option Explicit Sub test() Dim dataArea As Range Set dataArea = ActiveSheet.Range("A1") Do While Not IsEmpty(dataArea.Cells(1, 1)) Set dataArea = AddSums(dataArea) Loop End Sub Private Function AddSums(ByRef businessUnitStart As Range) As Range '--- loops through cells following the 'Start' range given, ' and accumulates the range of accounts to summarize ' RETURNS the start of the next business unit range Dim accountRow As Range Dim account As String Set accountRow = businessUnitStart.Offset(1, 0) Dim sumArea As Range Do While Left$(accountRow.Cells(1, 1).Value2, 2) = "PL" account = accountRow.Cells(1, 1).Value2 If (Left$(account, 8) = "PL211010") Or (Left$(account, 8) = "PL203000") Then '--- add this account to the sum formula If sumArea Is Nothing Then Set sumArea = accountRow.Cells(1, 4) Else Set sumArea = Union(sumArea, accountRow.Cells(1, 4)) End If End If Set accountRow = accountRow.Offset(1, 0) Loop If Not sumArea Is Nothing Then Dim accountSum As Range Set accountSum = businessUnitStart.Offset(1, 6) accountSum.Formula = "=SUM(" & sumArea.Address & ")" End If Set AddSums = accountRow End Function
VBA 255 Maximum Characters in 1 Cell
I have a simple macro which adds the contents of each row in an excel sheet to a text file, with delimiters in between each cell value. This is done by running a for loop which iterates through each row and at the end of each iteration the values are added to the end of a String variable. Each ROW can have a lot of characters in it - I have not noticed any issues with that. However, when 1 single cell contains more than 255 characters, the concatenation fails. I am not sure if it is because of String limitations (I don't think that is the case), or if it is the Trim, Join, or Index functions that contains this limitation, or if it something else. Any help in getting some more insight would be appreciated. The line in question ('R' refers to the row/iteration number): stringVariable = stringVariable & vbNewLine & Application.Trim(Join(Application.Index(Cells(R, "A").Resize(, 25).Value, 1, 0), "|")) The error is: Run-time error '13': Type mismatch
The problem is with the Application.Index. How to debug? Let's have the active sheet with any values in row 1, all with less than 255 chars. But in one of this cells in row 1, for example in C1, should be the formula: =REPT("c",255) Now split the code into parts: Sub test() r = 1 v2DArray = Cells(r, "A").Resize(, 25).Value index1DArray = Application.Index(v2DArray, 1, 0) joinString = Join(index1DArray, "|") stringVariable = Application.Trim(joinString) MsgBox stringVariable End Sub This will work until you change the formula to =REPT("c",256). Now it will fail at Application.Index. Instead of the Application.Index you could do the following: Sub test2() r = 1 v2DArray = Cells(r, "A").Resize(, 25).Value ReDim v1DArray(LBound(v2DArray, 2) To UBound(v2DArray, 2)) As String For i = LBound(v2DArray, 2) To UBound(v2DArray, 2) v1DArray(i) = v2DArray(1, i) Next joinString = Join(v1DArray, "|") stringVariable = Application.Trim(joinString) MsgBox stringVariable End Sub
After experimenting using different combinations of the already present functions, I found that the macro finishes without issues when Index is not used. In the end I decided to add the value of each cell to the end of the string, one at a time. Solution: For i = 1 To numberOfColumns If i < numberOfColumns Then stringVariable = stringVariable & Trim(Cells(R, i).Value) & "|" Else stringVariable = stringVariable & Trim(Cells(R, i).Value) End If Next i stringVariable = stringVariable & vbNewLine
Matching rows from another sheet
I am attempting to copy results from another sheet based on the cell values on the active worksheet. i.e loop through every element in array "GWworkStations()" and find a match in column B of "Col List" sheet, and then copy the corresponding values in "C:E" to an array "MatchedEntries" so I can copy them back to the active sheet. The code is returning empty for "matchedRow", instead of reporting the row number. I am not getting an error. dim MatchedEntries() as string dim GWworkStations() as variant number_of_rows = ActiveSheet.UsedRange.Rows.Count With ActiveWorkbook.Worksheets("New Sheet") GWworkStations() = range("B2:B" & number_of_rows) End With ReDim MatchedEntries(1 To r) 'Size the array to hold the results. 'for every cell that is not empty in GWworkStations(), search through column B of 'Col List ' sheet. For i = 1 To number_of_rows 'matchedRow = Empty On Error Resume Next 'Keep running if Excel MATCH function below doesn't find a match. If Not IsEmpty(Cells(i, 1)) Then matchedRow = Application.WorksheetFunction.Match(GWworkStations(i, 1), range("Col List!B:B"), 0) If matchedRow = Empty Then Debug.Print "Empty " & matchedRow If IsEmpty(matchedRow) Then 'No match. MatchedEntries(i, 1) = "" 'GWworkStations(i, 1) Else 'If GWworkStations(i, 1) = GWworkStations(i - 1) Then If IsNumeric(matchedRow) Then 'Match was found. MatchedEntries(i, 1) = Application.WorksheetFunction.Index(range("List!C:E"), matchedRow, 1) Else 'MATCH function returned a non-numeric result. MatchedEntries(i, 1) = "" End If 'IsNumeric(MatchedRow) End If 'IsEmpty(MatchedRow) Else End If Next i range("E2:G" & number_of_rows) = MatchedEntries() 'Write the tag name results out to range E:G.
Excel doesn't like the space in the sheet name. You can fix this by using single quotes: Range("'Col List'!B:B"), or by replacing Range("Col List!B:B")with Sheets("Col List").Columns(2). You could also use the Range.Find method (which I would prefer): matchedRow = Sheets("Sheet 3").Columns(2).Find(str).Row
Spit Data in Single Cell into Multiple Rows
I have a data set with Names and Addresses in an Excel file in following format. Name1 134/47/1, adrs1, adr2, country Name2 adrs1, adrs2, country Name3 107/c, adrs3, adrs3, country etc… I want to split these data into multiple rows in following format Name1 134/47/1, adrs1, adrs2, country Name2 No 134/63, adrs1, adrs2, country etc… I tried following but it worked for one row cell only. Sub tst() Dim X As Variant X = Split(Range("A1").Value, ",") Range("A1").Resize(UBound(X) - LBound(X) + 1).Value = Application.Transpose(X) End Sub
The following macro might help you. You would have to select the very last cell in your table containing a multipart address. When you start the macro it will then work its way up to the top and insert address lines where needed (only in the current column) and then exit. Option Base 1 Sub trnsfrm() Dim i%, n%, ret(3, 1) Set r = Selection Do a = Split(r, ",") ret(1, 1) = Trim(a(0)) ret(2, 1) = Trim(a(1)) ret(3, 1) = Trim(a(2)) r.Range([a2], [a3]).Insert Shift:=xlDown r.Range([a1], [a3]) = ret If r.Row <= 4 Then Exit Do Set r = r.Offset(-4) Loop End Sub If you want to insert lines across the whole table you should replace the line (10) r.Range([a2], [a3]).Insert Shift:=xlDown by r.Range([a2], [a3]).EntireRow.Insert Shift:=xlDown Assumptions / Warning Since the macro will actually change your current table and 'undo' does not work with macros you should definitely save everything before you try it. The macro assumes that each address block consists of exactly 4 lines. If there are fewer or more lines to an address the maro will get out of sync and will very likely output garbage or halt.
I'm not sure whether your sample data had trailing commas on single values as a typo or if that is what accurately represents your data but that should be accounted for. A rogue comma as a suffix will create an extra element to the variant array thereby throwing off dimensions created by referencing the UBound function. Sub split_from_below_space() Dim rw As Long, v As Long, vVALs As Variant With Worksheets("Sheet1") 'set this worksheet reference properly! For rw = .Cells(Rows.Count, 1).End(xlUp).Row To 1 Step -1 .Cells(rw, 1) = Trim(.Cells(rw, 1).Value2) If CBool(InStr(1, .Cells(rw, 1).Value2, Chr(44) & Chr(32))) Then vVALs = Split(.Cells(rw, 1).Value2, Chr(44) & Chr(32)) .Cells(rw + 1, 1).Resize(UBound(vVALs), 1).EntireRow.Insert .Cells(rw, 1).Resize(UBound(vVALs) + 1, 1) = _ Application.Transpose(vVALs) For v = UBound(vVALs) - 1 To LBound(vVALs) Step -1 .Cells(rw, 1).Offset(v, 0) = _ Trim(.Cells(rw, 1).Offset(v, 0).Value2) & Chr(44) Next v End If Next rw End With End Sub You will need to insert rows to accommodate the data and that method is almost always (as in this case) better performed by working from the bottom to the top.