Reputation: 59
So I've been trying to tackle this from different angles and hitting walls each time. I'm not familiar with VBA so the data structures are also an issue. A pivot table isn't really an option as I'd like to add further logic later on.
The goal is to sort through several sheets of data, summing 2 values associated to keys, to output a column with unique keys and 2 columns with the sum of the respective values.
For example, Input:
John 10 5 TRUE
Mary 11 7 TRUE
John 1 1 FALSE
Susan 20 9 TRUE
Mary 0 0 TRUE
Susan 2 8 FALSE
John 3 12 TRUE
Desired output:
John 13 17
Mary 11 7
Susan 20 9
So far, I have been able to extract a list of unique names as a collection, and print it out. For efficiency's sake, I'd like to try and do the sums at the same time (instead of then going through all the data using unique names as an index to sum).
As far as I'm aware, I shouldn't be using a Collection
as they do not allow for updating the item value once set, and because a duplicate key would throw an error (thus not letting me update the values). I tried using Dictionaries
but having 2 objects (one for each associated value) with the same keys seems redundant and error-prone. Before trying Arrays
I'd like to know if I'm wasting my time and there's a better approach/data structure to use, keeping in mind I also need to output these 3 columns to a separate sheet, so an easy to print solution would be ideal.
Any guidance on how I should be doing this? It really doesn't seem that complicated, but I'm finding it hard to come across good documentation. Thanks in advance!
Upvotes: 0
Views: 76
Reputation: 12167
If you want to do it with VBA using a dictionary you could try the following code. I took your example data and assumed they are in sheet1 starting in A1. You need to modify the code for your needs.
Main Module
Option Explicit
Public Sub CreateReport()
' Turn off functionality such as auto calculations
'TurnOffFunctionality
' Read the data into a dictionary
Dim dict As Scripting.Dictionary
Set dict = ReadFromData()
' write the data to the report worksheet
WriteReport dict
' Turn functionality back on
'TurnOnFunctionality
End Sub
Module for reading the data
Option Explicit
Const COL_DATA_NAME = 1
Const COL_VALUE1 = 2
Const COL_VALUE2 = 3
Const COL_ADD = 4
Function ReadFromData() As Scripting.Dictionary
On Error GoTo EH
Dim dict As New Scripting.Dictionary
' Get the data range
Dim rgData As Range
Set rgData = Sheet1.Range("A1:D7") ' asumption data is in A1:D7
Dim FirstName As String
Dim value1 As Long, value2 As Long
Dim nameCalcs As Calcs
Dim add As Boolean
' Go through each row
Dim rgCurRow As Range
For Each rgCurRow In rgData.Rows
' Read the row data to variables
FirstName = rgCurRow.Cells(1, COL_DATA_NAME)
value1 = rgCurRow.Cells(1, COL_VALUE1)
value2 = rgCurRow.Cells(1, COL_VALUE2)
add = rgCurRow.Cells(1, COL_ADD)
' If FirstName one is not already in dictionary then add
If Not dict.Exists(FirstName) Then
Set nameCalcs = New Calcs
dict.add FirstName, nameCalcs
End If
' Update the data holder for each FirstName with new values based on the current values
If add Then
dict(FirstName).Sum1 = dict(FirstName).Sum1 + value1
dict(FirstName).Sum2 = dict(FirstName).Sum2 + value2
End If
Next rgCurRow
Set ReadFromData = dict
Done:
Exit Function
EH:
' Your error message
End Function
Module for writing the data
Option Explicit
Const REP_COL_FNAME = 1
Const REP_COL_SUM1 = 2
Const REP_COL_SUM2 = 3
Public Sub WriteReport(dict As Scripting.Dictionary)
On Error GoTo EH
' Clear the Report area
'ClearReportArea You need to do that on your own
' Write the report data
WriteDataToReport dict
Done:
Exit Sub
EH:
MsgBox Err.Description & ". Procedure is: Report_Write.TurnOnFunctionality."
End Sub
Private Sub WriteDataToReport(dict As Scripting.Dictionary)
On Error GoTo EH
' Get variable to track the rows
Dim rowCnt As Long
rowCnt = 1
' Go through each FirstName in the dictionary
Dim k As Variant
For Each k In dict.Keys
' Write the data to the report sheet from the data holder
Dim rgStart As Range
Set rgStart = Sheet1.Range("F1")
With dict(k)
rgStart.Offset(rowCnt, REP_COL_FNAME) = k
rgStart.Offset(rowCnt, REP_COL_SUM1) = .Sum1
rgStart.Offset(rowCnt, REP_COL_SUM2) = .Sum2
End With
rowCnt = rowCnt + 1
Next k
Done:
Exit Sub
EH:
' Your error mesaage
End Sub
And the class Calcs
Option Explicit
Public Sum1 As Long
Public Sum2 As Long
Upvotes: 1
Reputation: 96753
There is a tiny trick to allow you to do this efficiently.
Say in input data is in worksheet input and we have another worksheet called output.
In output cell B1 enter:
=IF(A1="","",SUMPRODUCT(--(input!$A$1:$A$999=A1)*(input!$D$1:$D$999=TRUE)*(input!$B$1:$B$999)))
and copy down. In output cell C1 enter:
=IF(A1="","",SUMPRODUCT(--(input!$A$1:$A$999=A1)*(input!$D$1:$D$999=TRUE)*(input!$C$1:$C$999)))
and copy down.
The output sheet is now ready to receive the unique names from column A of the input sheet (basically the formulas in output will not have to be re-entered or modified; if there are no names in column A, then B and C will appear empty). You can populate column A with:
Sub GetNames()
Dim s1 As Worksheet, s2 As Worksheet
Set s1 = Sheets("input")
Set s2 = Sheets("output")
s2.Columns(1).Clear
s1.Columns(1).Copy s2.Columns(1)
s2.Columns(1).RemoveDuplicates Columns:=1, Header:=xlNo
End Sub
Upvotes: 0