Dictionary from data table / dictionary to worksheet
Good day,
I have a data sheet that contains sales by department per week in the following format:
Week1 Week2 Week3 ...
Dept1 10 20 10
Dept1 20 10 30
Dept1 30 30 20
Dept2 20 20 30
Dept2 20 20 10
Dept3 50 40 60
...
What I need to do is create a small report that summarizes the sales per department. According to the following pattern:
Week1 Week2 Week3
Dept1 60 60 60
Dept2 40 40 40
Dept3 50 40 60
Total 150 140 160
The number of lines in one department varies. This report should then be printed into a spreadsheet.
From what I understand it can be done using dictionaries or collections. So far I have been able to calculate the amounts for each week, however I am not clear on how to transfer these results to the worksheet. I tried to pass the sums to an array, but it didn't work.
This is the code I have. It calculates the sums per week correctly, then frees the collection and calculates it again the next week. So, the main problem I am having is how to write these results to a worksheet.
Dim collection As collection
Dim dataitems As Itemlist 'defined in classmodule
Dim key As String
Dim item As Double
Dim row As Long, column As Long
Dim lstrow As Long, lstcolumn As Long
Set collection = New collection
columnindex = 3 'that is the column where name of departments appear
lstrow = Sheet1.Cells(Sheet1.Rows.Count, column).End(xlUp).row
lstcolumn = Sheet1.Cells(1, Sheet1.Columns.Count).End(xlToLeft).column
For column = 5 To lstcolumn 'column 5 is where the weekly data start
For row = 2 To lstrow 'first 1 contains titles
key = CStr(Sheet1.Cells(row, "C").Value2)
item = CDbl(Sheet1.Cells(row, column).Value2)
Set dataitems = Nothing: On Error Resume Next
Set dataitems = collection(key): On Error GoTo 0
If dataitems Is Nothing Then
Set dataitems = New Itemlist
dataitems.key = key
collection.Add dataitems, key
End If
With dataitems
.Sum = .Sum + item
.Itemlist.Add item
End With
Next
Set collection = New collection
Next
Any help is appreciated. Thank.
source to share
You may have working code, but I want to show you a different approach to achieving your goal.
This approach has three things.
1 - Control your unique keys (division names) in the dictionary like keys.
2 - Your weekly sums to be stored in an array as values ββin your dictionary.
3-Sum your unique names using
Application.SumIf
in one line.
The end result of your dictionary will look like this (I used your template for demonstration and simple comparison):
dict = {key1: value1, key2: value2, key3: value3)
For example:
dict = {"Dept1" :( 60,60,60), "Dept2" :( 40,40,40), "Dept3" :( 50,40,60)}
As you can see, the values ββare arrays that store the weekly sums of the division names.
However, these arrays are not declared for every department name. They are actually arrays inside another array:
arr1 = (arr1_1 (), arr1_2 (), arr1_3 ())
For example:
arr1 = ((60,60,60), (40,40,40), (50,40,60))
Now if you want to get dept3 week totals, basically it is
arr1 (2) which is (50,40,60)
If you want to get the totals for the week, then
arr1 (2) (1) which is 40
Hope you have an idea. One more thing, before we start, you have commented out your code:
'which is the column that displays the department names
'column 5 is where the weekly launch starts
'first 1 contains headers
So I did the same, here is the code:
Sub ArrayMyDictionary()
Dim dict As Object, lastrow As Long, lastcol As Long, i As Long, j As Long, c As Long
Dim arr1() As Variant, arr2() As Variant
Set dict = CreateObject("Scripting.Dictionary")
With Worksheets("Sheet1")
lastrow = .Cells(.Rows.Count, 3).End(xlUp).Row
lastcol = .Cells(1, .Columns.Count).End(xlToLeft).Column
ReDim arr1(c) 'array1 initial size 0, later on size is number dept
ReDim arr2(lastcol - 5) 'array2 size is number of weeks
For i = 2 To lastrow
If Not dict.Exists(.Cells(i, 3).Value) Then 'check if Dept not exists in dict
ReDim Preserve arr1(c)
arr1(c) = arr2() ' create empty array2 (size is number of weeks) as an element of current array1
For j = 5 To lastcol
arr1(c)(j - 5) = Application.SumIf(.Range(.Cells(2, 3), .Cells(lastrow, 3)), .Cells(i, 3).Value, .Range(.Cells(2, j), .Cells(lastrow, j)))
Next
dict(.Cells(i, 3).Value) = arr1(c) ' create key (Dept name) and value (an array that holds relevant weekly sums)
c = c + 1
End If
Next
End With
'this part will print out your results to Sheet2
With Worksheets("Sheet2")
Dim key As Variant
For Each key In dict.Keys
.Cells(.Rows.Count, 1).End(xlUp).Offset(1, 0) = key 'last empty row - print key
For j = 0 To lastcol - 5
.Cells(.Rows.Count, 1).End(xlUp).Offset(0, j + 1) = dict(key)(j) 'same row proceed to cell on right - print each element in array inside value
Next j
Next key
.Cells(.Rows.Count, 1).End(xlUp).Offset(1, 0) = "Total" 'last row - calculate totals
For j = 0 To lastcol - 5
.Cells(.Rows.Count, 1).End(xlUp).Offset(0, j + 1) = Application.WorksheetFunction.Sum(.Columns(j + 2)) 'same row proceed to cell on right - sum of columns
Next j
End With
End Sub
source to share
Your code is indeed nearly complete and works well enough, although there are some habits I would like to comment on that will save you a lot of pain when trying to debug it.
First of all, set the set of variables that apply to your Workbook
and Worksheets
. This will make it very clear which cells and which sheets are referenced and will keep everything in order. Also, ALWAYS use Option Explicit
.
Dim thisWB As Workbook
Dim thisWS As Worksheet
Dim destWS As Worksheet
Set thisWB = ThisWorkbook
Set thisWS = thisWB.Sheets("Sheet1")
Set destWS = thisWB.Sheets("Sheet2")
Then don't give your variable with the same name as the variable type ( collection As collection
). Not only is this confusing, but type names are reserved words in any compiler. Use variable names that describe in more detail why you are declaring and using them.
Dim deptTotal As Itemlist
Dim deptWeeklyTotals As collection
Set deptWeeklyTotals = New collection
Since you've decided to hardcode some of the columns and rows (that's okay), you should use these values ββas constants. Later, if these values ββchange, you only need to change them in one place.
Const DEPT_NAME_COL As Long = 3
Dim lastRow As Long
Dim lastCol As Long
lastRow = thisWS.Cells(thisWS.Rows.Count, DEPT_NAME_COL).End(xlUp).row
lastCol = thisWS.Cells(1, thisWS.Columns.Count).End(xlToLeft).column
Const WEEK1_COL As Long = 5
Const FIRST_DATA_ROW As Long = 2
In my example code, you will see that I am declaring my variables as close as possible to the location they are using the first time. This should reinforce Type
each variable and make sure it is initialized to an acceptable value. Here's your loop with these concepts in place:
Dim i As Long
Dim j As Long
Dim needsDeptLabels As Boolean
needsDeptLabels = True
For i = WEEK1_COL To lastCol
For j = FIRST_DATA_ROW To lastRow
Dim deptName As String
Dim weekTotal As Double
deptName = CStr(thisWS.Cells(j, DEPT_NAME_COL).Value2)
weekTotal = CDbl(thisWS.Cells(j, i).Value2)
Set deptTotal = Nothing
On Error Resume Next
Set deptTotal = deptWeeklyTotals(deptName)
On Error GoTo 0
If deptTotal Is Nothing Then
Set deptTotal = New Itemlist
deptTotal.key = deptName
deptWeeklyTotals.Add deptTotal, deptName
End If
With deptTotal
.sum = .sum + weekTotal
.Itemlist.Add weekTotal
End With
Next j
'--- set up for the next week
Set deptWeeklyTotals = New collection
Next i
Finally, to return the totals to (a) the worksheet, it only takes one loop inside the main loop for each column:
'--- output the results to the summary table
For j = 1 To deptWeeklyTotals.Count
If needsDeptLabels Then
Set deptTotal = deptWeeklyTotals(j)
destWS.Cells(j, DEPT_NAME_COL).Value = deptTotal.key
End If
destWS.Cells(j, i).Value = deptTotal.sum
Next j
needsDeptLabels = False '- only need to put the labels in once
So now your routine is:
Option Explicit
Sub DeptSummary()
Dim thisWB As Workbook
Dim thisWS As Worksheet
Dim destWS As Worksheet
Set thisWB = ThisWorkbook
Set thisWS = thisWB.Sheets("Sheet1")
Set destWS = thisWB.Sheets("Sheet2")
Dim deptTotal As Itemlist
Dim deptWeeklyTotals As collection
Set deptWeeklyTotals = New collection
Const DEPT_NAME_COL As Long = 3
Dim lastRow As Long
Dim lastCol As Long
lastRow = thisWS.Cells(thisWS.Rows.Count, DEPT_NAME_COL).End(xlUp).row
lastCol = thisWS.Cells(1, thisWS.Columns.Count).End(xlToLeft).column
Const WEEK1_COL As Long = 5
Const FIRST_DATA_ROW As Long = 2
Dim i As Long
Dim j As Long
Dim needsDeptLabels As Boolean
needsDeptLabels = True
For i = WEEK1_COL To lastCol
For j = FIRST_DATA_ROW To lastRow
Dim deptName As String
Dim weekTotal As Double
deptName = CStr(thisWS.Cells(j, DEPT_NAME_COL).Value2)
weekTotal = CDbl(thisWS.Cells(j, i).Value2)
Set deptTotal = Nothing
On Error Resume Next
Set deptTotal = deptWeeklyTotals(deptName)
On Error GoTo 0
If deptTotal Is Nothing Then
Set deptTotal = New Itemlist
deptTotal.key = deptName
deptWeeklyTotals.Add deptTotal, deptName
End If
With deptTotal
.sum = .sum + weekTotal
.Itemlist.Add weekTotal
End With
Next j
'--- output the results to the summary table
For j = 1 To deptWeeklyTotals.Count
If needsDeptLabels Then
Set deptTotal = deptWeeklyTotals(j)
destWS.Cells(j, DEPT_NAME_COL).Value = deptTotal.key
End If
destWS.Cells(j, i).Value = deptTotal.sum
Next j
needsDeptLabels = False '- only need to put the labels in once
'--- set up for the next week
Set deptWeeklyTotals = New collection
Next i
End Sub
source to share