0
votes

I'm trying to write a VBA code to do a SumIf in an array, as my worksheet has about 200k rows, and multiple formulas in other sheets, using a formula or .WorksheetFunction.SumIf takes too long. Also, my worksheet is unsorted as the last rows are always the most recent data added.

My Sheet has 15 columns, but i'm only using A B C D for the sumif. A,C,D = contain text and column B contains the numbers I want to sum.

I've tried the following code which works just fine, but takes about 5 min to complete the calulations.

Dim i As Long

With Sheets("Sheet1")

x = .Cells(.Rows.Count, 1).End(xlUp).Row
For i = 2 To x
    .Cells(i, 7).Value2 = Application.WorksheetFunction.SumIfs(.Range("B:B"), _
                    .Range("C:C"), .Range(("C") & i), _
                    .Range("A:A"), .Range(("A") & i), _
                    .Range("D:D"), .Range(("D") & i))
        Next i
End With

End Sub

I've started working on an array VBA to replace the sumif as it would be much faster but I can't manage to get it working properly. The code I'm using is below.

Dim i As Long
Dim arrRAM As Variant
Dim arrType As Variant
Dim arrR As Variant
Dim arrO As Variant
Dim arrX As Variant
Dim arrY As Variant
Dim arrD As Variant
Dim dic As Object: Set dic = CreateObject("Scripting.Dictionary")

With Sheets("Sheet2")
    x = .Cells(.Rows.Count, 1).End(xlUp).Row
    arrRAM = .Cells(2, 2).Resize(x - 1).Value2
    arrType = .Cells(2, 3).Resize(x - 1).Value2
    arrR = .Cells(2, 1).Resize(x - 1).Value2
    arrO = .Cells(2, 4).Resize(x - 1).Value2
    arrX = .Cells(2, 5).Resize(x - 1, 2).Value2
    arrY = .Cells(2, 6).Resize(x - 1).Value2
    arrD = .Cells(2, 7).Resize(x - 1).Value2

For i = LBound(arrRAM, 1) To UBound(arrRAM, 1)
    arrY(i, 1) = arrType(i, 1) & arrR(i, 1) & arrO(i, 1)
    arrX(i, 1) = arrType(i, 1) & arrR(i, 1) & arrO(i, 1)
    arrX(i, 2) = arrRAM(i, 1)
Next i

For x = LBound(arrX, 1) To UBound(arrX, 1)
    dic(arrX(x, 1)) = arrX(x, 2)
Next x

tot = 0
For i = LBound(arrX, 1) To UBound(arrX, 1)
      If dic.Exists(arrY(i, 1)) Then
        tot = tot + arrX(i, 2)
    End If
    arrD(i, 1) = tot
    Next i

Debug.Print arrY(1, 1)
    .Cells(2, 6).Resize(UBound(arrD, 1)).Value2 = arrD

End With
End Sub

The idea behind it was to concatenate A,C & D into a single array. Then get another array which has the concatenated values + column B. Then it should search for the concatenated values from the first array in the second one(it looks like it does this part just fine), then it should do the sum.

The issue comes when I have to add up the values, it just takes the first value in column B then adds the next value into the first one. Below you can the results on sample data for the normal SumIf Formula/First Vba code and the second vba code.

Is there a way to fix my vba code to output the same results as the first one/sumif formula? Any help is appreciated.

Blockquote

3

3 Answers

1
votes

this works with variant arrays:

     With Worksheets("Sheet1")
        Dim x As Long
        x = .Cells(.Rows.Count, 1).End(xlUp).Row

        Dim rngArr() As Variant
        rngArr = .Range(.Cells(2, 1), .Cells(x, 4)).Value

        Dim outArr As Variant
        ReDim outArr(1 To x, 1 To 1)

        Dim i As Long
        For i = LBound(rngArr, 1) To UBound(rngArr, 1)
            Dim j As Long
            For j = LBound(rngArr, 1) To UBound(rngArr, 1)
                If rngArr(i, 1) = rngArr(j, 1) And rngArr(i, 3) = rngArr(j, 3) And rngArr(i, 4) = rngArr(j, 4) Then
                    outArr(i, 1) = outArr(i, 1) + rngArr(j, 2)
                End If
            Next j
        Next i

        .Cells(2, 7).Resize(UBound(outArr, 1), 1).Value2 = outArr
    End With

enter image description here

1
votes

Try this code, please. It is fast, using array and working only in memory. All calculated values are written form the array at once, at the end of the code. But it would be fast enough for such a big range, only if the same pairs of occurrences are in a big number...

Private Sub testSumIfInArray() 'super tare, super fast
 Dim sh As Worksheet, arrI As Variant, arrF As Variant, lastR As Long
 Dim i As Long, j As Long, pCount As Long, d As Object

  Set sh = ActiveSheet
  lastR = sh.Range("A" & Cells.Rows.Count).End(xlUp).row
  arrI = sh.Range("A2:D" & lastR).value
  ReDim arrF(1 To UBound(arrI, 1), 1 To 1)
  Set d = CreateObject("Scripting.Dictionary")

  For i = 1 To lastR - 1
    If Not d.Exists(UCase(arrI(i, 1) & arrI(i, 3) & arrI(i, 4))) Then
        For j = 1 To lastR - 1
            If UCase(arrI(i, 1)) = UCase(arrI(j, 1)) And _
                   UCase(arrI(i, 3)) = UCase(arrI(j, 3)) And _
                       UCase(arrI(i, 4)) = UCase(arrI(j, 4)) Then
                pCount = pCount + arrI(j, 2)
            End If
        Next j
        d(UCase(arrI(i, 1) & arrI(i, 3) & arrI(i, 4))) = pCount
        arrF(i, 1) = pCount: pCount = 0
    Else
        arrF(i, 1) = d(UCase(arrI(i, 1) & arrI(i, 3) & arrI(i, 4)))
    End If
  Next
  sh.Range("E2").Resize(UBound(arrF, 1), 1).value = arrF
End Sub

The big advantage of such a code, since you said that your sheet is updated with new rows, to run the code only looking for values in the last added rows (of course, reported to all existing range). In this way, it would be extremely fast.

0
votes

I've managed to do this by working around this answer. And it's pretty fast with my amount of data. (1.5 seconds)

The code allows for multiple criteria as long as you concatenate them.

It will concatenate them in another sheet, calculate the sumif there and output the result to your desired sheet + column.

Sub Sort1st()
Dim x As Long
Dim i As Long
Dim arr1() As Variant
Dim arr2() As Variant

Sheets.Add(After:=Sheets(Sheets.Count)).Name = "Help"
With Sheets("Source")
    x = .Cells(.Rows.Count, 1).End(xlUp).Row
    arr1 = .Cells(1, 1).Resize(x, 33).Value2
End With
With Sheets("Help")
    arr2 = .Cells(1, 1).Resize(x, 2).Value2
End With
For i = 2 To x
    arr2(i, 1) = arr1(i, 5) & arr1(i, 31) & arr1(i, 32)
    arr2(i, 2) = arr1(i, 12)
    Next i
With Sheets("Help")
.Cells(1, 1).Resize(UBound(arr2, 1), UBound(arr2, 2)).Value2 = arr2
End With
Erase arr1, arr2
Call Sumifs(1)
End Sub

Private Sub Sumifs(Criteria As Long)
With Sheets("Help")
Dim SumRange, DataNumber, HelpColumn, SumifColumn, LastRow As Long
SumRange = Criteria + 1
DataNumber = Criteria + 2
HelpColumn = Criteria + 3
SumifColumn = Criteria + 4
LastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
Columns(DataNumber).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Columns(HelpColumn).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Columns(SumifColumn).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove

Cells(2, DataNumber).Value = 1
Cells(2, DataNumber).AutoFill Destination:=Range(Cells(2, DataNumber), Cells(LastRow, DataNumber)), Type:=xlFillSeries

Range(Cells(1, Criteria), Cells(LastRow, SumifColumn)).Sort Key1:=Columns(Criteria), Order1:=xlAscending, Header:=xlYes
ActiveSheet.Sort.SortFields.Clear

With Range(Cells(2, HelpColumn), Cells(LastRow, HelpColumn))
    .FormulaR1C1 = "=IF(RC[-3]=R[-1]C[-3], RC[-2] + R[-1]C,RC[-2])"
End With

With Range(Cells(2, SumifColumn), Cells(LastRow, SumifColumn))
    .FormulaR1C1 = "=IF(RC[-4]=R[+1]C[-4], R[+1]C, RC[-1])"
    .Value = .Value
End With

Columns(HelpColumn).Delete

Range(Cells(1, Criteria), Cells(LastRow, SumifColumn)).Sort Key1:=Columns(DataNumber), Order1:=xlAscending, Header:=xlYes
ActiveSheet.Sort.SortFields.Clear

Columns(DataNumber).Delete
End With
Dim x As Long
Dim arr As Variant
With Sheets("Help")
x = .Cells(.Rows.Count, 1).End(xlUp).Row
    arr = .Cells(1, 3).Resize(x, 1).Value2
End With
With Sheets("Source")
.Cells(1, 35).Resize(UBound(arr, 1)) = arr
End With
Erase arr
Application.DisplayAlerts = False
Worksheets("Help").Delete
Application.DisplayAlerts = True
End Sub