Converting weekly data in a table to monthly data using VBA - excel

I have a table of hours against weeks (start of the week is a Sunday). The weekly data goes up to 12-16 months dependent on user input. I want to create a VBA macro which will iterate through this table of weekly hours data and convert the columns into monthly data.
Example:
All October 2021 related columns will collapse into 1 column called Oct-21. This will also combine the hours. 2nd row in the image below would equal 4+3+4+0= therefore value would be 11 in the new combined column's 2nd row.
My current thinking was calculating the Sundays between the start date and the last date which is below:
Dim d As Date, format As String, w As Long, FirstSunday As String
format = format(lastMonth, "Medium Date")
d = DateSerial(Year(format), Month(format), 1)
w = Weekday(d, vbSunday)
FirstSunday = d + IIf(w <> 1, 8 - w, 0)
Any ideas on how to do this?

Not sure how you want to group the weeks into months as some months will have 5 weeks. This code inserts a column when the month changes and then fills it with a sum formula for the relevant week columns. It assumes the dates are on row 1 , the task numbers in column 1 and the first week is in column 2.
Option Explicit
Sub ByMonth()
Dim wb As Workbook, ws As Worksheet
Dim LastCol As Long, LastRow As Long, c As Long, n As Long
Dim dt As Date
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
LastCol = ws.Cells(1, Columns.Count).End(xlToLeft).Column
LastRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
' scan cols from right to left insert new columns
Application.ScreenUpdating = False
For c = LastCol + 1 To 3 Step -1
' add columns on month change
If Month(ws.Cells(1, c)) <> Month(ws.Cells(1, c - 1)) Then
ws.Columns(c).Insert
With ws.Columns(c)
.HorizontalAlignment = xlCenter
'.Interior.Color = RGB(255, 255, 200)
.Font.Bold = True
.Cells(1).NumberFormat = "#"
End With
End If
Next
' scan left to right filling new cols with sum() formula
' hide weekly columns
LastCol = ws.Cells(1, Columns.Count).End(xlToLeft).Column
n = 0
For c = 2 To LastCol + 1
If ws.Cells(1, c) = "" Then
dt = ws.Cells(1, c - 1)
ws.Cells(1, c) = MonthName(Month(dt), True) & " " & Year(dt)
ws.Cells(2, c).Resize(LastRow - 1).FormulaR1C1 = "=SUM(RC[-" & n & "]:RC[-1])"
n = 0
Else
ws.Columns(c).EntireColumn.Hidden = True
n = n + 1
End If
Next
' copy visible month columns to sheet2
ws.Cells.SpecialCells(xlCellTypeVisible).Copy
With wb.Sheets("Sheet2")
.Activate
.Range("A1").PasteSpecial xlPasteValues
.Range("A1").Select
End With
' end
ws.Columns.Hidden = False
Application.ScreenUpdating = True
Application.CutCopyMode = False
MsgBox "Done"
End Sub

Please, try the next code. It assumes that in column A:A, starting from the 6th row, there are (not sorted) tasks. If they are sorted, the code will run without problem, too. It uses arrays and a dictionary and mostly working in memory, should be very fast for big ranges:
Sub SumWeeksMonths()
Dim sh As Worksheet, sh1 As Worksheet, lastR As Long, arrWk, arrMonths, arrTasks
Dim i As Long, k As Long, j As Long, El, arr, arrFin, dict As New Scripting.Dictionary
Set sh = ActiveSheet 'use there the sheet to be processed
Set sh1 = sh.Next 'use here the sheet where the processed result to be returned
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row (in column A:A)
arrWk = sh.Range(sh.Range("B5"), sh.cells(5, sh.Columns.count).End(xlToLeft)).Value 'place the Week headers in a 2D array
ReDim arrMonths(UBound(arrWk, 2) - 1)'redim the 1D array to keep the unique munths, at a maximum size
For i = 1 To UBound(arrWk, 2) - 1 'create the array of (only) months:
If month(DateValue(arrWk(1, i))) <> month(DateValue(arrWk(1, i + 1))) Then
k = k + 1: arrMonths(k) = Format(DateValue(arrWk(1, i + 1)), "mmm-yyyy")
Else
arrMonths(k) = Format(DateValue(arrWk(1, i)), "mmm-yyyy")
End If
Next i
ReDim Preserve arrMonths(k) 'preserve only the existing Date elements
For Each El In sh.Range("A4:A" & lastR).Value
dict(El) = 1 'extract the unique tasks (only to count them for ReDim the necessary array)
Next El
'place all the range to be processed in an array (for faster iteration):
arr = sh.Range("A5", sh.cells(lastR, sh.cells(5, sh.Columns.count).End(xlToLeft).Column)).Value
ReDim arrFin(1 To UBound(dict.Keys) + 1, 1 To UBound(arrMonths) + 2) 'reDim the final array to keep processed data
ReDim arrTasks(UBound(arrMonths)) 'redim the array to temporarily keep the array of each task summ
dict.RemoveAll: k = 0 'clear the dictionary and reitinialize the K variable
For i = 2 To UBound(arr) 'iterate between the main array elements:
If Not dict.Exists(arr(i, 1)) Then 'if the Task key does not exist:
For Each El In arrMonths 'iterate between each month in arrMonths:
For j = 2 To UBound(arr, 2) 'iterate between all arr columns for the i row:
If month(DateValue(arr(1, j))) = month(El) Then 'if column months is a specific arrMonths column:
arrTasks(k) = arrTasks(k) + arr(i, j) 'sumarize everything in the arrTask each element
End If
Next j
k = k + 1 'increment k, for the next month
Next El
dict.Add arr(i, 1), arrTasks 'create the dictionary key with the tasks array as item
ReDim arrTasks(UBound(arrMonths)): k = 0 'reinitialize arrTasks and k variable
Else 'if dictionary (task) key exists:
For Each El In arrMonths
For j = 2 To UBound(arr, 2)
If month(DateValue(arr(1, j))) = month(El) Then
arrTasks(k) = dict(arr(i, 1))(k) + arr(i, j) 'add the sum to the allready existing elements
End If
Next j
k = k + 1
Next El
dict(arr(i, 1)) = arrTasks 'make the item the updaded array
ReDim arrTasks(UBound(arrMonths)): k = 0 'reinitialize arrTasks and k variable
End If
Next i
'place the processed values in final array (arrFin):
For i = 0 To UBound(arrMonths) 'firstly the headers:
arrFin(1, i + 2) = arrMonths(i)
Next i
'Extract the tasks value for each month and place in the final array appropriate columns:
For i = 0 To dict.count - 1 'iterate between the dictionary elements:
arrFin(i + 2, 1) = dict.Keys(i) 'place the task in the array first column, starting from the second row
For j = 0 To UBound(dict.items(i)) 'iterate between the dictionary item array elements
arrFin(i + 2, j + 2) = dict.items(i)(j) 'place the appropriate array elements in the final array (arrFin)
Next j
Next i
'drop the final array at once and make some formatting:
With sh1.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2))
.Value = arrFin
With .rows(1)
.Font.Bold = True
.Interior.ColorIndex = 20
.BorderAround 1
End With
.EntireColumn.AutoFit
.BorderAround 1
End With
sh1.Activate 'to see the processing result...
MsgBox "Ready..."
End Sub
Please, test it and send some feedback.

Related

How to Copy Data into an Array/Manipulate It/Paste to new sheet

SETUP
I have an Excel file with my source data in Columns in A to J.
In Column K there's a 'Send Type' value which can either be "Many" or "Single".
In Column L there's a 'Send Count' value, which is numeric ("N").
OBJECTIVE
copy the source data
insert N-1 rows
paste that data N-1 times into those rows.
I'd like the result to be the data pasted N times.
If N is 1, there's no copy/insert/paste required
if N is greater than 1, the result should be the data displayed N times.
Example Excel Sheet
CURRENT VBA
Sub Copy_PROD_Paste_Send_Count()
Dim Copy_Row As Integer
Dim Send_Count As Variant
Dim TargetMapCount As Integer
Dim ProgressCount As Integer
Dim Send_Type As String
Dim ProgressTarget As Integer
Copy_Row = 1
TargetMapCount = Application.WorksheetFunction.SumIf(Range("K:K"), "Many", Range("L:L"))
Send_Type = Cells(Copy_Row, "K")
ProgressTarget = Application.WorksheetFunction.Count(Range("A:A")) + Application.WorksheetFunction.SumIf(Range("K:K"), "Many", Range("L:L")) - Application.WorksheetFunction.CountIf(Range("K:K"), "Many")
Application.ScreenUpdating = False
Do While (Cells(Copy_Row, "A") <> "")
Send_Count = Cells(Copy_Row, "L")
Send_Type = Cells(Copy_Row, "K")
If (Send_Type = "Many" And (Send_Count > 1) And IsNumeric(Send_Count)) Then
Range(Cells(Copy_Row, "A"), Cells(Copy_Row, "L")).Copy
Range(Cells(Copy_Row + 1, "A"), Cells(Copy_Row + Send_Count - 1, "L")).Select
Selection.Insert Shift:=xlDown
Copy_Row = Copy_Row + Send_Count - 1
ProgressCount = Range("A" & Rows.Count).End(xlUp).Row
Application.StatusBar = "Updating :" & ProgressCount - 1 & " of " & ProgressTarget & ": " & Format((ProgressCount - 1) / ProgressTarget, "0%")
End If
Copy_Row = Copy_Row + 1
Loop
End Sub
PROBLEM STATEMENT
The macro executes up to about 2-3k rows before crashing. I need to run up to 15k.
I understand I should try to copy the data into an array, manipulate it within that array and then paste the results back to a new sheet. I can't work out how.
Here is a routine that copies the input table to an array and then builds an output array (in memory) from the inputs, before dumping the output array back to the worksheet.
Note: It ignores the Single/Many column and instead just uses the value in the last column to determine how many times to repeat the output.
Sub RepeatData()
'Declarations
Dim LastRow As Long
Dim LastColumn As Long
Dim m As Long, n As Long, o As Long, r As Long, c As Long
With ActiveSheet
'Find Last Row of table
LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
'Find Last Column of table
LastColumn = .Cells(2, .Columns.Count).End(xlToLeft).Column
'Copy table to array
Dim ArrInput
ArrInput = ActiveSheet.Range(ActiveSheet.Cells(1, 1), ActiveSheet.Cells(LastRow, LastColumn)).Value
'sum up multiples (total of last column in table) so we know how big the output array needs to be
OutputRows = 1 + Application.WorksheetFunction.Sum(Cells(2, LastColumn).Resize(UBound(ArrInput) - 1, 1))
'Create output array for filling
ReDim ArrOutput(1 To OutputRows, 1 To LastColumn)
'Copy data across
o = 1 ' start outputting to index 1 of ArrOutput
For r = 1 To LastRow
If r = 1 Then ' if on header row
m = 1 ' set repeat to once (set m to 1)
Else ' if not on header row
m = ArrInput(r, LastColumn) ' set repeat to value in last column
End If
For n = 1 To m ' loop to repeat
For c = 1 To LastColumn ' cycle across columns
ArrOutput(o, c) = ArrInput(r, c) 'copy value
Next
o = o + 1 ' increment output index
Next
Next
End With
'Write output array to sheet, 5 rows below the end of the input table
ActiveSheet.Cells(LastRow + 5, 1).Resize(UBound(ArrOutput), UBound(ArrOutput, 2)).Value = ArrOutput
End Sub

Extract multiple match values without duplicates

I have a set of matching values as shown:
The input is a table with Order number in the first column and dates in the seventh column.
I would like to extract all the matching dates from the seventh column and display only the 'unique dates' in the columns against each matching order value.
If there are no matching values in the input, it should return blank values in output.
I use Excel 2016. The inputs are in sheet 2.
I managed to get the dates with array index formula but it is slow with large data.
If you have access to the new array functions UNIQUE & FILTER then:
Using the sample data below
In cell E1: =UNIQUE(A1:A10)
In cell F1: =TRANSPOSE(UNIQUE(FILTER(B1:B10,A1:A10=E1)))
Then drag the formula from F1 down to the last cell which will populate your desired table.
Please, try the next VBA solution. It should be very fast, using two dictionaries and arrays, mostly working in memory. It will return the processed result starting from "J2" cell. It can return anywhere, you should only change "J2" cell with the cell range you need, even being in another sheet:
Sub extractUniqueValues_Dat()
Dim sh As Worksheet, lastR As Long, arr, arrIt, arrFin, Z As Long
Dim dict As Object, dictI As Object, i As Long, k As Long
Set sh = ActiveSheet
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row
arr = sh.Range("A2:G" & lastR).value 'place the range to be processed in an array, for faster iteration
Set dict = CreateObject("Scripting.Dictionary") 'set first necessary dictionary
For i = 1 To UBound(arr) 'iterate between the array rows and load the dictionary:
If Not dict.Exists(arr(i, 1)) Then 'if the key does not exist:
Set dictI = CreateObject("Scripting.Dictionary") 'set a new dictionary
dictI.Add arr(i, 7), vbNullString 'create a key of the new dictionary using first Date occurrence
dict.Add arr(i, 1), dictI 'create a dictionary key as Value and add the new dictionary as item
If dictI.count > Z Then Z = dictI.count 'extract maximum number of Date occurrences
Else
dict(arr(i, 1))(arr(i, 7)) = vbNullString 'if the key of the item dictionary does not exist it is added, with an empty item
If dict(arr(i, 1)).count > Z Then Z = dict(arr(i, 1)).count 'extract maximum number of Date occurrences
End If
Next i
ReDim arrFin(1 To dict.count, 1 To Z + 1) '+ 1, to make place for the dictionary key (in first column)
'fill the arrFin array:
For i = 0 To dict.count - 1
arrFin(i + 1, 1) = dict.Keys()(i) 'place the main dictionary key in the first column of the final array
For k = 1 To dict.Items()(i).count
arrFin(i + 1, 1 + k) = dict.Items()(i).Keys()(k - 1) 'place each date (extracted from the item dictionary keys) in the following columns
Next k
Next i
'build the header:
Dim arrH: arrH = Evaluate("TRANSPOSE(ROW(1:" & Z & "))")
arrH = Split("Match Value|Data " & Join(arrH, "|Data "), "|")
'drop the final aray content and apply a little formatting:
With sh.Range("J2").Resize(UBound(arrFin), UBound(arrFin, 2))
.value = arrFin
With .rows(1).Offset(-1)
.value = arrH
.Font.Bold = True
.HorizontalAlignment = xlCenter
End With
.EntireColumn.AutoFit
End With
MsgBox "Ready..."
End Sub
Please send some feedback after testing it.
Edited:
Please, test the next version. It will work even if the customer orders will not be unique (in K:K column)... This code will also extract only unique values from the mentioned range. It will also check if there are values in the processed sheet which cannot be found in K:K, and returns in the sheet being processed, starting from "M1". Please, use the real sheet where K:K necessary column exists, when set shK sheet!
Private Sub extractUniqueValues_Dat()
Dim shK As Worksheet, lastRK As Long, sh As Worksheet, lastR As Long, arr, arrK, arrIt, arrFin, Z As Long
Dim dict As Object, dictI As Object, dictK As Object, i As Long, k As Long
Set sh = ActiveSheet
lastR = sh.Range("B" & sh.rows.count).End(xlUp).row 'last row in B:B
arr = sh.Range("B2:H" & lastR).Value 'place the range to be processed in an array, for faster iteration
Set shK = Worksheets("sheet KK") 'use here the necessary sheet (with values in K:K)!!!
lastRK = shK.Range("K" & shK.rows.count).End(xlUp).row 'last row in K:K
arrK = shK.Range("K2:K" & lastRK).Value
Set dictK = CreateObject("Scripting.Dictionary") 'set first necessary dictionary
Set dict = CreateObject("Scripting.Dictionary") 'set first necessary dictionary
'place the UNIQUE values in a dictionary, as keys and all unique date, for all accurrences in an item array:
For i = 1 To UBound(arr) 'iterate between the array rows and load the dictionary:
If Not dict.Exists(arr(i, 1)) Then 'if the key does not exist:
Set dictI = CreateObject("Scripting.Dictionary") 'set a new dictionary
dictI.Add arr(i, 7), vbNullString 'create a key of the new dictionary using first Date occurrence
dict.Add arr(i, 1), dictI 'create a dictionary key as Value and add the new dictionary as item
If dictI.count > Z Then Z = dictI.count 'extract maximum number of Date occurrences
Else
dict(arr(i, 1))(arr(i, 7)) = vbNullString 'if the key of the item dictinary does not exist it is added, with an empty item
If dict(arr(i, 1)).count > Z Then Z = dict(arr(i, 1)).count 'extract maximum number of Date occurrences
End If
Next i
'place the UNIQUE vales from K:K column, only as keys:
For i = 1 To UBound(arrK)
dictK(arrK(i, 1)) = vbNullString
Next i
ReDim arrFin(1 To dictK.count, 1 To Z + 3) '+ 1, to make splace for the dictionary key (in first column)
'fill the arrFin array:
For i = 0 To dictK.count - 1
arrFin(i + 1, 1) = dictK.Keys()(i) 'place the main dictionary keyi in the first column of the final array
If dict.Exists(dictK.Keys()(i)) Then
For k = 1 To dict(dictK.Keys()(i)).count
arrFin(i + 1, 3 + k) = dict(dictK.Keys()(i)).Keys()(k - 1) 'place each date (extracted from the item dictionary keys) in the following columns
Next k
End If
Next i
'check if there are missing values from sheet with processed data:
Dim arrMiss, KK As Long, boolMiss As Boolean
ReDim arrMiss(dict.count)
For i = 0 To dict.count - 1
If Not dictK.Exists(dict.Keys()(i)) Then
arrMiss(KK) = dict.Keys()(i): KK = KK + 1
End If
Next i
'build the header:
Dim arrH: arrH = Evaluate("TRANSPOSE(ROW(1:" & Z & "))")
arrH = Split("Match Value|x|y|Data " & Join(arrH, "|Data "), "|")
'drop the final aray content and apply a little formatting:
With sh.Range("M2").Resize(UBound(arrFin), UBound(arrFin, 2))
.CurrentRegion.Value = "" 'if the previous return dropped more rows than the actual one...
.Value = arrFin
With .rows(1).Offset(-1)
.Value = arrH
.Font.Bold = True
.HorizontalAlignment = xlCenter
End With
.EntireColumn.AutoFit
End With
If KK > 0 Then
ReDim Preserve arrMiss(KK - 1)
MsgBox "Missing Values: " & vbCrLf & Join(arrMiss, vbCrLf), vbInformation, "Please, check..."
boolMiss = True
End If
If Not boolMiss Then MsgBox "Ready..."
End Sub
Send some feedback after testing it, please...

How can i use VBA Code to Copy and Paste Specific Cells if Condition is Met in two or more worksheets to different areas of another worksheet

Looking for a little more help please. I was here a month ago a RiskyPenguin gave me a great bit of code. I would like to add to this.
This is the part that works:
So if the "invoice" spreadsheet (sheet 5), if cell G4 (for example is I111) matches any of the data in the first column of the "income" spreadsheet (sheet 1) (starting at row 6) then the corresponding data in columns 2 3, 8 & 9 will copy over to the "invoice" spreadsheet in columns 2, 3, 4 & 5 (starting at row 13).
Sub FindAndCopyData2()
Dim shData As Worksheet, shReport As Worksheet
Set shData = Sheet1
Set shReport = Sheet6
Dim strInvoceNumber As String
strInvoceNumber = shReport.Cells(4, "E").Value
Dim intLastRow As Integer
intLastRow = shData.Cells(Rows.Count, 1).End(xlUp).Row
Dim intReportRow As Integer
intReportRow = 13
shReport.Range("B13:E20").ClearContents
Dim i As Integer
For i = 1 To intLastRow
If shData.Cells(i, 1).Value2 = strInvoceNumber Then
shReport.Cells(intReportRow, 2).Value2 = shData.Cells(i, 3).Value2
shReport.Cells(intReportRow, 3).Value2 = shData.Cells(i, 4).Value2
shReport.Cells(intReportRow, 4).Value2 = shData.Cells(i, 8).Value2
shReport.Cells(intReportRow, 5).Value2 = shData.Cells(i, 9).Value2
intReportRow = intReportRow + 1
End If
Next i
End Sub
I would then like to (hopefully using the same search)
Take the "invoice" spreadsheet (sheet 5), if cell G4 (for example is I111) matches any of the data in the second column of the "expenses" spreadsheet (sheet 2) (starting at row 11) then the corresponding data in columns 3, 5, & 7 will copy over to the "invoice" spreadsheet in columns 2, 4 & 6 (starting at row 13).
Is this possible or does it have to be a separate piece of programming?
Many Thanks for any advise.
Assuming this could be useful for others I made a function out of it and refactored the initial code to handle the copy in memory. I setup your first lookup so you just need to edit the variables to get your second lookup:
Option Explicit
''''''''''''''''''''''''''''''''''''''
''Main Sub
''''''''''''''''''''''''''''''''''''''''''''''''''
Sub main()
'Set some vars
Dim sourceArr, targetArr, sourceCls, targetCls, sourceStartRw As Long, targetStartRw As Long, dict As Object, j As Long, sourceLookupCl As Long, Matchkey As Long
''''''''''''''''''''''''''''''''''''''
''Lookup 1
''''''''''''''''''''''''''''''''''''''''''''''''''
Matchkey = Sheet5.Range("G4").Value2 'lookupKey
sourceCls = Split("2,3,8,9 ", ",") 'Columns to copy from
targetCls = Split("2,3,4,5", ",") 'Columns to copy to
sourceStartRw = 6
targetStartRw = 13
sourceLookupCl = 1 'matching column
'get data in memory = array
sourceArr = Sheet1.Range("A1").CurrentRegion.Value2
'call our function
targetArr = reorder(sourceArr, sourceCls, targetCls, sourceStartRw, sourceLookupCl, Matchkey)
'dump to sheet
With Sheet5
.Range(.Cells(targetStartRw, 1), .Cells(UBound(targetArr) + targetStartRw - 1, UBound(targetArr, 2))).Value2 = targetArr
End With
''''''''''''''''''''''''''''''''''''''
''Lookup 2 => change source and target cols to your need
''''''''''''''''''''''''''''''''''''''''''''''''''
Matchkey = Sheet5.Range("G4").Value2
sourceCls = Split("2,3,8,9 ", ",")
targetCls = Split("2,3,4,5", ",")
sourceStartRw = 6
targetStartRw = 13 'must be the same as previous lookup if you want to keep the targetArr from previous lookups
sourceLookupCl = 1
'get data in memory = array
sourceArr = Sheet1.Range("A1").CurrentRegion.Value2
'call our function keeping the data from the first lookup
targetArr = reorder(sourceArr, sourceCls, targetCls, sourceStartRw, sourceLookupCl, Matchkey, targetArr)
'dump to sheet
With Sheet5
.Range(.Cells(targetStartRw, 1), .Cells(UBound(targetArr) + targetStartRw - 1, UBound(targetArr, 2))).Value2 = targetArr
End With
End Sub
''''''''''''''''''''''''''''''''''''''
''Supporting function
''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function reorder(sourceArr, sourceCls, targetCls, sourceStartRw As Long, sourceLookupCl As Long, Matchkey As Long, Optional targetArr) As Variant
Dim dict As Object, j As Long
'if the target array overlaps the previous lookups pass it to the function
If IsMissing(targetArr) Then
ReDim targetArr(1 To UBound(sourceArr), 1 To UBound(sourceArr, 2))
End If
'build a dict to compare quickly
Set dict = CreateObject("Scripting.Dictionary") 'create dictionary lateB
For j = 1 To UBound(sourceArr) 'traverse source
dict(sourceArr(j, sourceLookupCl)) = Empty
Next j
'check if key exists in dict and copy data
Dim i As Long, ii As Long ': ii = 1
If dict.Exists(Matchkey) Then
For j = sourceStartRw To UBound(sourceArr)
For i = 1 To UBound(sourceArr, 2)
If i = sourceCls(ii) Then
targetArr(j - sourceStartRw + 1, targetCls(ii)) = sourceArr(j, i)
ii = IIf(ii < UBound(sourceCls), ii + 1, ii)
End If
Next i
ii = 0
Next j
End If
reorder = targetArr
End Function

Filter out entire rows if group value is below 10

I am trying to remove rows from a spreadsheet in VBA if the sum total of value exceeds a specific amount.
For example, if I have the following data, names in A1 down and values in A2 down:
I would like to remove all rows where the total sum of the value in row A does not reach 10 or above in row B, this would leave the following results:
Thomas = 18 and John = 15 so all rows with Thomas and John are kept.
All other rows would be deleted.
Please note that I will always know that the data is in row A and B but I do not know how many rows there will be and need to execute until the first blank cell.
It worked. You can see this here:
Sub run()
Dim rowIndex, countSameRow, sumSameRow As Integer
sumSameRow = Cells(1, 2)
rowIndex = 2
countSameRow = 1
While IsEmpty(Cells(rowIndex, 1)) = False
If (Cells(rowIndex, 1) = Cells(rowIndex - 1, 1)) Then
sumSameRow = sumSameRow + Cells(rowIndex, 2)
countSameRow = countSameRow + 1
Else
If (sumSameRow < 10) Then
Rows(rowIndex - 1 & ":" & rowIndex - countSameRow).Delete
rowIndex = rowIndex - countSameRow
End If
countSameRow = 1
sumSameRow = Cells(rowIndex, 2)
End If
If IsEmpty(Cells(rowIndex + 1, 1)) Then
If (sumSameRow < 10) Then
Rows(rowIndex & ":" & rowIndex - countSameRow + 1).Delete
End If
End If
rowIndex = rowIndex + 1
Wend
End Sub
Totally agree you should write your own code first, but I couldn't help but write some starting code for you. See if the below fits your purpose:
Sub Test()
Dim lr As Long, x As Long
Dim arr As Variant, rng As Range
Dim dict1 As Object: Set dict1 = CreateObject("Scripting.Dictionary")
Dim dict2 As Object: Set dict2 = CreateObject("Scripting.Dictionary")
With Sheet1 'Change according to your sheets CodeName
'Get all of your data form A:B in memory (array)
lr = .Cells(.Rows.Count, 1).End(xlUp).Row
arr = .Range("A1:B" & lr)
'Step through the array and fill up our two dictionaries
For x = LBound(arr) To UBound(arr)
If dict1(arr(x, 1)) <> "" Then
dict1(arr(x, 1)) = Join(Array(dict1(arr(x, 1)), x & ":" & x), ",")
Else
dict1(arr(x, 1)) = x & ":" & x
End If
dict2(arr(x, 1)) = dict2(arr(x, 1)) + arr(x, 2)
Next x
'Step through our second dictionary and check if value < 10
For Each Key In dict2.keys
If dict2(Key) < 10 Then
If Not rng Is Nothing Then
Set rng = Union(rng, .Range(dict1(Key)))
Else
Set rng = .Range(dict1(Key))
End If
End If
Next Key
'If any where below 10, this Range object has been filled, so delete it.
If Not rng Is Nothing Then
rng.Delete
End If
End With
End Sub
Here is another method that uses Autofilter and SUMIF to delete the lines.
This assumes there is a header row, if not then add a row first.
It adds a sumif in column C and filters all that is less than 10, then deletes them.
Then removes column C again.
Sub filter()
Range("C1").Value = "Sum"
Lastrow = Range("A" & Rows.Count).End(xlUp).Row
Range("C2:C" & Lastrow).Formula = "=sumif(A:A,A2,B:B)"
Range("A2").AutoFilter ' add a filter to table
ActiveSheet.Range("$A$1:$C$" & Lastrow).AutoFilter Field:=3, Criteria1:="<10", Operator:=xlAnd ' filter all below 10
ActiveSheet.Range("A2:C" & Lastrow).SpecialCells(xlCellTypeVisible).EntireRow.Delete ' delete them
Range("A1").AutoFilter ' remove filter again
Columns("C:C").EntireColumn.Delete ' remove column C
End Sub

How to use loop to SUM categories?

I am trying to use a loop with vba to sum values from one worksheet to another. I am struggling with writing my code to match values from Sheet 4 and if the value matches then sum the categories from Sheet 1, if not then skip to the next office. I would also like to exclude certain categories from being included in the SUM loop for example, exclude "Book". Currently my macro is writing to Sheet3. Here is my code:
Option Explicit
Sub test()
Dim a, i As Long, ii As Long, dic As Object
Set dic = CreateObject("Scripting.Dictionary")
dic.CompareMode = 1
a = Sheets("sheet1").Cells(1).CurrentRegion.Value
With CreateObject("Scripting.Dictionary")
.CompareMode = 1
For i = 2 To UBound(a, 1)
If Not dic.Exists(a(i, 1)) Then dic(a(i, 2)) = dic.Count + 2
If Not .Exists(a(i, 1)) Then
Set .Item(a(i, 1)) = CreateObject("Scripting.Dictionary")
.Item(a(i, 1)).CompareMode = 1
End If
.Item(a(i, 1))(a(i, 2)) = .Item(a(i, 1))(a(i, 2)) + a(i, 3)
Next
ReDim a(1 To .Count + 1, 1 To dic.Count + 1)
a(1, 1) = Sheets("sheet1").[a1]
For i = 0 To dic.Count - 1
a(1, i + 2) = dic.Keys()(i)
Next
For i = 0 To .Count - 1
a(i + 2, 1) = .Keys()(i)
For ii = 2 To UBound(a, 2)
a(i + 2, ii) = .items()(i)(a(1, ii)) + 0
Next
Next
End With
With Sheets("sheet3").Cells(1).Resize(UBound(a, 1), UBound(a, 2))
.EntireColumn.ClearContents
Sheets("sheet1").[a1].Copy .Rows(1)
.Value = a: .Columns.AutoFit: .Parent.Activate
End With
End Sub
This is how the data looks
and this is the output that is desired
In this example, we will use arrays to achieve what you want. I have commented the code so that you shall not have a problem understanding it. However if you still do then simply ask :)
Input
Output
Logic
Find last row and last column of input sheet
Store in an array
Get unique names from Column A and Row 1
Define output array
Compare array to store sum
Create new sheet and output to that sheet
Code
Option Explicit
Sub Sample()
Dim ws As Worksheet, wsNew As Worksheet
Dim tempArray As Variant, OutputAr() As Variant
Dim officeCol As New Collection
Dim productCol As New Collection
Dim itm As Variant
Dim lrow As Long, lcol As Long, totalsum As Long
Dim i As Long, j As Long, k As Long
'~~> Input sheet
Set ws = Sheet1
With ws
'~~> Get Last Row and last column
lrow = .Range("A" & .Rows.Count).End(xlUp).Row
lcol = .Cells(1, Columns.Count).End(xlToLeft).Column
'~~> Store it in a temp array
tempArray = .Range(.Cells(2, 1), .Cells(lrow, lcol)).Value
'~~> Create a unique collection using On error resume next
On Error Resume Next
For i = LBound(tempArray) To UBound(tempArray)
officeCol.Add tempArray(i, 1), CStr(tempArray(i, 1))
productCol.Add tempArray(i, 2), CStr(tempArray(i, 2))
Next i
On Error GoTo 0
End With
'~~> Define you new array which will hold the desired output
ReDim OutputAr(1 To officeCol.Count + 1, 1 To productCol.Count + 1)
'~~> Store the rows and columns in the array
i = 2
For Each itm In officeCol
OutputAr(i, 1) = itm
i = i + 1
Next itm
i = 2
For Each itm In productCol
OutputAr(1, i) = itm
i = i + 1
Next itm
'~~> Calculate sum by comparing the arrays
For i = 2 To officeCol.Count + 1
For j = 2 To productCol.Count + 1
totalsum = 0
For k = LBound(tempArray) To UBound(tempArray)
If OutputAr(i, 1) = tempArray(k, 1) And _
OutputAr(1, j) = tempArray(k, 2) Then
totalsum = totalsum + tempArray(k, 3)
End If
Next k
OutputAr(i, j) = totalsum
Next j
Next i
'~~> Create a new sheet
Set wsNew = ThisWorkbook.Sheets.Add
'~~> Outout the array
wsNew.Range("A1").Resize(officeCol.Count + 1, productCol.Count + 1).Value = OutputAr
End Sub

Resources