Home » excel » vba – Copying data from cells and adding it up Excel visual basic

vba – Copying data from cells and adding it up Excel visual basic

Posted by: admin May 14, 2020 Leave a comment

Questions:

I would like to make a macro that copies numbers that fall under the same category and add them up separately for each category. For instance Cells in column c contain the name of the product than 4 columns to the right is the number of sold products. I would like to add up all the entries in the number of sold products that fall under the same product together for each product and write it out to a predefined cell. So far I have come up with this

Sub find()
Dim XXX As Range
Dim myTotal As Long
Dim name As String
Dim name2 As String

name = Range("C2")

For Each XXX In Range("C2:C99999")
name2 = ActiveCell.Value

If name <> name2 Then
    Dim aa As Integer
    aa = 1
    Cells(aa, 8).Value = name
    Cells(aa, 9).Value = myTotal
    name = name2
    myTotal = 0
    aa = aa + 1
End If

If InStr(XXX.Value, name2) > 0 Then
    myTotal = myTotal + XXX.Offset(0, 4).Value
End If

Next XXX

End Sub

Any tips or guidelines would be appreciated and I hope the explanation makes sense.

How to&Answers:

Here’s a faster basic approach:

Sub find()

    Dim dict As Object, names, nums, r As Long
    Dim sht As Worksheet

    Set sht = ActiveSheet

    Set dict = CreateObject("scripting.dictionary")

    names = Range("C2:C99999").Value
    nums = Range("C2:C99999").Offset(0, 4).Value

    For r = 1 To UBound(names)
        dict(names(r, 1)) = dict(names(r, 1)) + nums(r, 1)
    Next r

    WriteCounts dict, sht.Range("J1")

End Sub

Sub WriteCounts(dict As Object, rngStart As Range)
    Dim k
    For Each k In dict.keys
        rngStart.Value = k
        rngStart.Offset(0, 1).Value = dict(k)
        Set rngStart = rngStart.Offset(1, 0)
    Next k
End Sub

Answer:

The Dictionary ‘SumIf’ Feature

VBA Dictionary Solution

  • Credits to Tim Williams and
    his
    solution
    .
  • Why would OP want a VBA solution when there is a perfectly good Excel
    solution? When there are tens of thousands of records and as many or
    many times more formulas, the workbook tends to get slow. So by adding
    the SUMIF formula we’re adding another bunch of them slowing down
    even more. And we don’t know the unique values, which we could find
    using another seriously slowing down formula.
  • So VBA will do this in a split second, or will it? I created a new
    worksheet with 60000 records and with 1000 unique ones to try to
    prove it.
  • SumIf Solution: The first idea was to adjust all the ranges, get the unique values using Advanced Filter and then use
    Worksheetfunction.SumIf. SumIf took its time, 17s, and when I
    added some formulas it went above 20s.
  • Array Loop Solution: This one was again using Advanced Filter but this time the idea was to put everything into arrays and loop
    through them and adding the values to another array one by one. This
    time the loop took its time. After some tweaking it went down to 13s
    and stayed there even after adding formulas.
  • Advanced Filter did copy the unique values in less than 0.2s into
    the appropriate range, but the rest was taking too long.
  • Dictionary Solution: Tim Williams’ solution did initially do all this in 2.5s. How is that possible I thought, Advanced Filter is the god
    of unique values. Well, it isn’t, or at best it is only one of them. I saw
    this line in a loop in the code: dict(names(r, 1)) = dict(names(r, 1)) + nums(r, 1). It seemed like it was doing the heavy lifting in a split second which forced me to investigate (Dictionary Object (Microsoft), Excel VBA Dictionary: A Complete Guide (Paul Kelly) and produce a
    solution.

The Code

Sub SumIfToTarget3() ' Array Dictionary ... 0.2-0.3s

    ' Name
    Const cNsht As Variant = "Sheet2"   ' Name Worksheet Name/Index
    Const cNrow As Long = 1             ' Name First Row Number
    Const cNcol As Long = 3             ' Name Column Number
    Const cVcol As Long = 7             ' Value Column Number
    ' Target
    Const cTsht As Variant = "Sheet2"   ' Target Worksheet Name/Index
    Const cTrow As Long = 1             ' Target First Row Number
    Const cUcol As Long = 8             ' Unique Column Number
    Const cUnique As String = "Unique"  ' Unique Column Header
    Const cSumIf As String = "Total"    ' SumIf Column Header

    ' Create a reference to the Dictionary Object.
    '*******************************************************
    ' Early Binding (0.1s Faster)                          *
    ' You have to go to Tools>References and check (create *
    ' a reference to) "Microsoft Scripting Runtime" .      *
'    Dim dict As New Dictionary '                           *
    '*******************************************************
    '**************************************************
    ' Late Binding (0.1s Slower)                      *
    ' You don't need to create a reference.           *
    Dim dict As Object '                              *
    Set dict = CreateObject("Scripting.Dictionary") ' *
    '**************************************************

    Dim dk As Variant    ' Dictionary 'Counter' (For Each Control Variable)
    Dim CurV As Variant  ' Current Value
    Dim rngN As Range    ' Name Column Range, Last Used Cell in Name Column,
                         ' Name Range with Headers, Name Range
    Dim rngV As Range    ' Value Range
    Dim rngT As Range    ' Target Columns Range, Target Range
    Dim vntN As Variant  ' Name Array
    Dim vntV As Variant  ' Value Array
    Dim vntT As Variant  ' Target Array
    Dim i As Long        ' Name/Value Array Element (Row) Counter,
                         ' Target Array Row Counter, Target Array Rows Count
                         ' (Dictionary Items Count)

    ' Speed up.
    With Application
        .ScreenUpdating = False
        .Calculation = xlCalculationManual
    End With

    ' Handle Errors.
    On Error GoTo ErrorHandler

    ' In Unique Column
    With ThisWorkbook.Worksheets(cTsht).Columns(cUcol)
        ' Create a reference to Target Columns Range (rngT) i.e. the range in
        ' Unique Column (cUcol) from Target First Row (cTrow) to the bottom row
        ' of Target Worksheet (cTsht), resized by a column for SumIf Column (2).
        Set rngT = .Resize(.Rows.Count - cTrow + 1, 2).Offset(cTrow - 1)
    End With
    ' Clear contents of Target Columns Range (rngT).
    rngT.ClearContents
    ' Write Unique Column Header to 1st Cell of Target Columns Range.
    rngT.Cells(1) = cUnique
    ' Write SumIf Column Header to 2nd Cell of Target Columns Range.
    rngT.Cells(2) = cSumIf

    ' In Name Column
    With ThisWorkbook.Worksheets(cNsht).Columns(cNcol)
        ' Calculate Last Used Cell in Name Column.
        Set rngN = .find("*", , xlFormulas, , xlByColumns, xlPrevious)
        ' Calculate Name Range with headers.
        Set rngN = .Cells(cNrow).Resize(rngN.Row - cNrow + 1)
    End With
    ' Calculate Name Range (without headers).
    Set rngN = rngN.Resize(rngN.Rows.Count - 1).Offset(1)
    ' Copy Name Range (rngN) to Name Array (vntN).
    vntN = rngN
    ' Calculate Value Range (without headers).
    Set rngV = rngN.Offset(, cVcol - cNcol)
    ' Copy Value Range (rngV) to Value Array (vntV).
    vntV = rngV

    ' Loop through elements (rows) of Name Array.
    For i = 1 To UBound(vntN)
        ' Write element in current row (i) of Value Array (vntV) to Current
        ' Value.
        CurV = vntV(i, 1)
        ' Check if Current Value (CurV) is NOT a number.
        If Not IsNumeric(CurV) Then
            ' Assign 0 to Current Value.
            CurV = 0
        End If
        ' Add current element (row) in Name Array (vntN) and Current Value
        ' to the Dictionary. If the key to be added is new (not existing),
        ' the new key and the item will be added. But if the key exists, then
        ' the existing item will be increased by the value of the new item.
        ' This could be called "The Dictionary SumIf Feature".
        dict(vntN(i, 1)) = dict(vntN(i, 1)) + CurV
    Next

    ' Reset Name/Value Array Element (Row) Counter to be used as
    ' Target Array Row Counter.
    i = 0
    ' Resize Target Array to the number of items in the Dictionary.
    ReDim vntT(1 To dict.Count, 1 To 2)
    ' Loop through each Key (Item) in the Dictionary.
    For Each dk In dict.Keys
        ' Increase Target Array Row Counter (count Target Array Row).
        i = i + 1
        ' Write current Dictionary Key to element in current (row) and
        ' 1st column (Unique) of Target Array.
        vntT(i, 1) = dk
        ' Write current Dictionary Item to element in current (row) and
        ' 2nd column (SumIf) of Target Array.
        vntT(i, 2) = dict(dk)
    Next

    ' Calculate Target Range (rngT) from second row (2) of Target Columns
    ' Range (rngT) resized by Target Array Rows Count (i).
    Set rngT = rngT.Rows(2).Resize(i)
    ' Copy Target Array (vntT) to Target Range (rngT).
    rngT = vntT

ProcedureExit:

    ' Speed down.
    With Application
        .Calculation = xlCalculationAutomatic
        .ScreenUpdating = True
    End With

Exit Sub

ErrorHandler:

    MsgBox "An unexpected error occurred. Error '" & Err.Number & "': " _
            & Err.Description, vbCritical, "Error"
    GoTo ProcedureExit

End Sub

SUMIF?! An Excel Solution

This is more a question than an answer:

Could this be regarded as a simplified visual presentation of what you are trying to achieve?

You can use the following formula in cell I2:

=SUMIF(C$2:C$16,H2,G$2:G$16)

Adjust the ranges and copy down.

enter image description here

Advanced Filter Array Loop Solution

Sub SumIfToUnique2() ' Advanced Filter & Loop through Arrays, Add ... 13s

    ' Name
    Const cNsht As Variant = "Sheet2"   ' Name Worksheet Name/Index
    Const cNrow As Long = 1             ' Name First Row Number
    Const cNcol As Long = 3             ' Name Column Number
    Const cVcol As Long = 7             ' Value Column Number
    ' Unique
    Const cUsht As Variant = "Sheet2"   ' Unique Worksheet Name/Index
    Const cUrow As Long = 1             ' Unique First Row Number
    Const cUcol As Long = 8             ' Unique Column Number
    Const cSumIf As String = "Total"    ' SumIf Column Header
    Const cUnique As String = "Unique"  ' Unique Column Header

    Dim rngN As Range    ' Name Column Range, Last Used Cell in Name Column,
                         ' Name Range with Headers, Name Range
    Dim rngV As Range    ' Value Range
    Dim rngU As Range    ' Unique Column Range, Last Used Cell in Unique Column,
                         ' Unique Range
    Dim vntN As Variant  ' Name Array
    Dim vntV As Variant  ' Value Array
    Dim vntU As Variant  ' Unique Array
    Dim vntS As Variant  ' SumIf Array
    Dim i As Long        ' Name/Value Array Row Counter
    Dim k As Long        ' Unique/SumIf Array Row Counter
    Dim strN As String   ' Current Name (in Name Array)

    ' Speed up.
    With Application
        .ScreenUpdating = False
        .Calculation = xlCalculationManual
    End With

    ' Handle Errors.
    On Error GoTo ErrorHandler

    ' In Name Column
    With ThisWorkbook.Worksheets(cNsht).Columns(cNcol)
        ' Create a reference to Name Column Range (rngN) i.e. the range in
        ' Name Column (cNcol) from Name First Row (cNrow) to the bottom row
        ' of Name Worksheet (cNsht).
        Set rngN = .Resize(.Rows.Count - cNrow + 1).Offset(cNrow - 1)
    End With

    ' In Unique Column
    With ThisWorkbook.Worksheets(cUsht).Columns(cUcol)
        ' Create a reference to Unique Column Range (rngU) i.e. the range in
        ' Unique Column (cUcol) from Unique First Row (cUrow) to the bottom row
        ' of Unique Worksheet (cUsht).
        Set rngU = .Resize(.Rows.Count - cUrow + 1).Offset(cUrow - 1)
    End With
    ' Clear contents of Unique Column Range (rngU).
    rngU.ClearContents
    ' Calculate SumIf Column Range.
    ' Clear contents of SumIf Column Range.
    rngU.Offset(, 1).ClearContents

    ' Write unique values from Name Column Range (rngN), starting with the
    ' header (aka title), to Unique Column Range (rngU), starting in its
    ' First Row (1).
    rngN.AdvancedFilter xlFilterCopy, , rngU.Resize(1), True
    ' Calculate Unique Header Cell Range.
    ' Write Unique Column Header to Unique Header Cell Range.
    rngU.Resize(1) = cUnique
    ' Calculate SumIf Header Cell Range.
    ' Write SumIf Column Header to SumIf Header Cell Range.
    rngU.Resize(1).Offset(, 1) = cSumIf

    ' In Name Column
    With ThisWorkbook.Worksheets(cNsht).Columns(cNcol)
        ' Calculate Last Used Cell in Name Column.
        Set rngN = .find("*", , xlFormulas, , xlByColumns, xlPrevious)
        ' Calculate Name Range with headers.
        Set rngN = .Cells(cNrow).Resize(rngN.Row - cNrow + 1)
    End With
    ' Calculate Name Range (without headers).
    Set rngN = rngN.Resize(rngN.Rows.Count - 1).Offset(1)
    ' Copy Name Range (rngN) to Name Array (vntN).
    vntN = rngN
    ' Calculate Value Range (without headers).
    Set rngV = rngN.Offset(, cVcol - cNcol)
    ' Copy Value Range (rngV) to Value Array (vntV).
    vntV = rngV

    ' In Unique Column
    With ThisWorkbook.Worksheets(cUsht).Columns(cUcol)
        ' Calculate Last Used Cell in Unique Column.
        Set rngU = .find("*", , xlFormulas, , xlByColumns, xlPrevious)
        ' Calculate Unique Range with headers.
        Set rngU = .Cells(cUrow).Resize(rngU.Row - cUrow + 1)
    End With
    ' Calculate Unique Range (without headers).
    Set rngU = rngU.Resize(rngU.Rows.Count - 1).Offset(1)
    ' Copy Unique Range (rngU) to Unique Array (vntU).
    vntU = rngU

    ' Resize SumIf Array to size of Unique Array.
    ReDim vntS(1 To UBound(vntU), 1 To 1)
    ' Loop through elements (rows) of Name Array.
    For i = 1 To UBound(vntN)
        ' Write current value in Name Array (vntN) to Current Name (strN).
        strN = vntN(i, 1)
        ' Loop through elements (rows) of Unique/SumIf Array.
        For k = 1 To UBound(vntU)
            If vntU(k, 1) = strN Then
                vntS(k, 1) = vntS(k, 1) + vntV(i, 1)
                Exit For
            End If
        Next
    Next

    ' Calculate SumIf Range (from Unique Range (rngU)).
    ' Copy SumIf Array to SumIf Range.
    rngU.Offset(, 1) = vntS

ProcedureExit:

    ' Speed down.
    With Application
        .Calculation = xlCalculationAutomatic
        .ScreenUpdating = True
    End With

Exit Sub

ErrorHandler:

    MsgBox "An unexpected error occurred. Error '" & Err.Number & "': " _
            & Err.Description, vbCritical, "Error"
    GoTo ProcedureExit

End Sub

Advanced Filter SumIf Solution

Sub SumIfToUnique1() ' Advanced Filter & SumIf on Ranges ... 17-22s

    ' Name
    Const cNsht As Variant = "Sheet2"   ' Name Worksheet Name/Index
    Const cNrow As Long = 1             ' Name First Row Number
    Const cNcol As Long = 3             ' Name Column Number
    Const cVcol As Long = 7             ' Value Column Number
    ' Unique
    Const cUsht As Variant = "Sheet2"   ' Unique Worksheet Name/Index
    Const cUrow As Long = 1             ' Unique First Row Number
    Const cUcol As Long = 8             ' Unique Column Number
    Const cSumIf As String = "Total"    ' SumIf Column Header
    Const cUnique As String = "Unique"  ' Unique Column Header

    Dim rngN As Range    ' Name Column Range, Last Used Cell in Name Column,
                         ' Name Range with Headers, Name Range
    Dim rngV As Range    ' Value Range
    Dim rngU As Range    ' Unique Column Range, Last Used Cell in Unique Column,
                         ' Unique Range
    Dim vntU As Variant  ' Unique Array
    Dim vntS As Variant  ' SumIf Array
    Dim i As Long        ' Unique Array Row Counter

    ' Speed up.
    With Application
        .ScreenUpdating = False
        .Calculation = xlCalculationManual
    End With

    ' Handle Errors.
    On Error GoTo ErrorHandler

    ' In Name Column
    With ThisWorkbook.Worksheets(cNsht).Columns(cNcol)
        ' Create a reference to Name Column Range (rngN) i.e. the range in
        ' Name Column (cNcol) from Name First Row (cNrow) to the bottom row
        ' of Name Worksheet (cNsht).
        Set rngN = .Resize(.Rows.Count - cNrow + 1).Offset(cNrow - 1)
    End With

    ' In Unique Column
    With ThisWorkbook.Worksheets(cUsht).Columns(cUcol)
        ' Create a reference to Unique Column Range (rngU) i.e. the range in
        ' Unique Column (cUcol) from Unique First Row (cUrow) to the bottom row
        ' of Unique Worksheet (cUsht).
        Set rngU = .Resize(.Rows.Count - cUrow + 1).Offset(cUrow - 1)
    End With
    ' Clear contents of Unique Column Range (rngU).
    rngU.ClearContents
    ' Calculate SumIf Column Range.
    ' Clear contents of SumIf Column Range.
    rngU.Offset(, 1).ClearContents

    ' Write unique values from Name Column Range (rngN), starting with the
    ' header (aka title), to Unique Column Range (rngU), starting in its
    ' First Row (1).
    rngN.AdvancedFilter xlFilterCopy, , rngU.Resize(1), True
    ' Calculate Unique Header Cell Range.
    ' Write Unique Column Header to Unique Header Cell Range.
    rngU.Resize(1) = cUnique
    ' Calculate SumIf Header Cell Range.
    ' Write SumIf Column Header to SumIf Header Cell Range.
    rngU.Resize(1).Offset(, 1) = cSumIf

    ' In Name Column
    With ThisWorkbook.Worksheets(cNsht).Columns(cNcol)
        ' Calculate Last Used Cell in Name Column.
        Set rngN = .find("*", , xlFormulas, , xlByColumns, xlPrevious)
        ' Calculate Name Range with headers.
        Set rngN = .Cells(cNrow).Resize(rngN.Row - cNrow + 1)
    End With
    ' Calculate Name Range (without headers).
    Set rngN = rngN.Resize(rngN.Rows.Count - 1).Offset(1)
    ' Calculate Value Range (without headers).
    Set rngV = rngN.Offset(, cVcol - cNcol)

    ' In Unique Column
    With ThisWorkbook.Worksheets(cUsht).Columns(cUcol)
        ' Calculate Last Used Cell in Unique Column.
        Set rngU = .find("*", , xlFormulas, , xlByColumns, xlPrevious)
        ' Calculate Unique Range with headers.
        Set rngU = .Cells(cUrow).Resize(rngU.Row - cUrow + 1)
    End With
    ' Calculate Unique Range (without headers).
    Set rngU = rngU.Resize(rngU.Rows.Count - 1).Offset(1)
    ' Copy Unique Range to Unique Array.
    vntU = rngU

    ' Resize SumIf Array to size of Unique Array.
    ReDim vntS(1 To UBound(vntU), 1 To 1)

    ' Loop through elements (rows) of SumIf/Unique Array.
    For i = 1 To UBound(vntS)
        ' Write result of SumIf funtion to current element (row) of SumIf Array.
        vntS(i, 1) = WorksheetFunction.SumIf(rngN, vntU(i, 1), rngV)
    Next

    ' Calculate SumIf Range (from Unique Range (rngU)).
    ' Copy SumIf Array to SumIf Range.
    rngU.Offset(, 1) = vntS

ProcedureExit:

    ' Speed down.
    With Application
        .Calculation = xlCalculationAutomatic
        .ScreenUpdating = True
    End With

Exit Sub

ErrorHandler:

    MsgBox "An unexpected error occurred. Error '" & Err.Number & "': " _
            & Err.Description, vbCritical, "Error"
    GoTo ProcedureExit

End Sub