So I am trying to make a VBA scripts that changes all indirect formula in a selection into direct reference, aim is to improve performance of my excel workbook. Below is the code:
Call manual
Dim continue As Integer
continue = MsgBox("This cannot be undone. Continue anyway?", vbOKCancel)
If continue <> vbOK Then Exit Sub
Dim formula_array() As Variant
row_cnt = Selection.Rows.count
col_cnt = Selection.Columns.count
ReDim formula_array(1 To row_cnt, 1 To col_cnt)
If row_cnt = 1 And col_cnt = 1 Then
formula_array(1, 1) = Selection.formula
Else
formula_array = Selection.formula
End If
'for some reason formula_array = Selection.formula gives an error when I select only one cell
count = 0
Dim i As Integer, y As Integer
For i = 1 To row_cnt
For y = 1 To col_cnt
frmula = formula_array(i, y)
oldfunc = find_full_formula(frmula, "indirect(")
Do While (oldfunc <> "")
newfunc = Application.Evaluate(oldfunc)
If IsError(newfunc) Then
newfunc = ""
End If
oldfunc = "indirect(" & oldfunc & ")"
formula_array(i, y) = Replace(formula_array(i, y), oldfunc, newfunc, 1, -1, vbTextCompare)
frmula = formula_array(i, y)
oldfunc = find_full_formula(frmula, "indirect(")
count = count + 1
Loop
Next y
Next i
Dim temp As String
Selection.formula = formula_array
MsgBox count
Call auto
Here the find_full_formula function gives arguments of any function, input is the start of that function and the whole formula. So if you have a formula"Indirect("A1:B2")" then the result of this function would be "A1:B2".
The whole script works very well for normal ranges except when I try to run in on a column of an excel table where the selection also includes the first cell of the column (first cell of data, so not the header) then the result is that all cells in that column have the same formula as the first cell. What is also interesting is that if I select all cells of a column of the table except the first one then the result is fine but only when the first cell is also involved then the problem arises. It obviously looks like some auto-fill feature but I have turned off all such settings that I could find and still this issue isn't solved.
okay, I am adding below a much simpler version of VBA code to highlight my problem:
Dim arr(1 To 4, 1 To 1) As Variant
arr(1, 1) = "2+2"
arr(2, 1) = "=3+2"
arr(3, 1) = "=4+2"
arr(4, 1) = "=5+2"
Range("A2:A5").Formula = arr
this code above works just fine, however the one below results in "=2+2" as formula for each cell of my table.
Dim arr(1 To 4, 1 To 1) As Variant
arr(1, 1) = "=2+2"
arr(2, 1) = "=3+2"
arr(3, 1) = "=4+2"
arr(4, 1) = "=5+2"
Range("A2:A5").Formula = arr
Table in excel looks something like this:
Excel Table
I found a solution that works in all cases I checked out, but it's not beautiful - consider it as a workaround:
set Application.AutoCorrect.AutoFillFormulasInLists = False
set formula to cells by looping them (one by one)
None of these alone sets the formulas as expected if selection matches a ListObject.DataBodyRange.
Sub Test()
' select a range that fits
' the following arrays dimensions
Dim arr(1 To 4, 1 To 2) As Variant
arr(1, 1) = "=2+2": arr(1, 2) = "=12+2"
arr(2, 1) = "=3+2": arr(2, 2) = "=13+2"
arr(3, 1) = "=4+2": arr(3, 2) = "=14+2"
arr(4, 1) = "=5+2": arr(4, 2) = "=15+2"
' deactivate AutoFillFormulasInLists; store setting to restore
Dim bAutoFill As Boolean
bAutoFill = Application.AutoCorrect.AutoFillFormulasInLists
Application.AutoCorrect.AutoFillFormulasInLists = False
Selection.ClearContents
' `Selection.FormulaR1C1 = arr` does NOT work in case of
' Selection = ListObject.DataBodyRange
' => loop cells (slower and more lines of code)
Dim i As Long, j As Long
For i = 1 To UBound(arr, 1)
For j = 1 To UBound(arr, 2)
Selection(i, j).FormulaR1C1 = arr(i, j)
Next j
Next i
Application.AutoCorrect.AutoFillFormulasInLists = bAutoFill
End Sub
Hopefully somebody else will paste a more straightforward solution!
Related
I have 2-dimensional array and I would like to inspect each element in a specific row with If-Then statements and assign assign values to the next row depending on the outcome of the If-Then statements? What is the correct syntax for looping through the elements of a row in a 2-d array?
Please, try using the next Sub:
Sub changeRow(arr As Variant, iR As Long, strTxt As String)
Dim i As Long
For i = LBound(arr, 2) To UBound(arr, 2) '(arr, 2) to determine the number of columns
arr(iR, i) = arr(iR, i) & strTxt
Next i
End Sub
Of course, it can be designed to do whatever you need on the respective row. Even extending parameters to be used.
It can easily be tested in the next way:
Sub testIterate2DArrayRow()
Dim sh As Worksheet, arr, arrR, iRow As Long, strAdd As String
Set sh = ActiveSheet
iRow = 2 'the array row to be iterated
strAdd = " - XX" 'string to be added to each row element (instructional example)
arr = sh.Range("A2:D6").value 'the easiest way to create a 2D array
arrR = Application.Index(arr, iRow, 0) 'create a 1D slice of the row to be iterated/modified
'if you need only iterating to extract something, you may stop here
'and iterate between its elements...
Debug.Print Join(arrR, "|") 'just to visually see the row content
changeRow arr, iRow, strAdd 'iterate on the iRow row (and modify something)
Debug.Print Join(Application.Index(arr, iRow, 0), "|") 'visual evidence of the modification...
End Sub
Edited:
I will let the above code for other people liking to learn the general concept.
Please, test the next code, which should process the array as (I understood) you need.
Its first lines only create the opportunity to easily check the concept. So, you should place the necessary bays on an Excel sheet, from "A1" to "J1" and run the above code. It will return the processed array starting from "L1":
Sub analizeBays()
Dim sh As Worksheet, BayRay(), i As Long
Set sh = ActiveSheet
BayRay = sh.Range("A1:J4").value 'only to easily test the concept
For i = LBound(BayRay, 2) To UBound(BayRay, 2)
If BayRay(1, i) <= 10 Then
BayRay(2, i) = 2035
BayRay(3, i) = 2005
BayRay(4, i) = 1005
ElseIf BayRay(1, i) > 10 And BayRay(1, i) <= 12 Then
BayRay(2, i) = 2022
BayRay(3, i) = 1032
BayRay(4, i) = 4344
End If
Next i
'drop the processed array content starting from "L1")
sh.Range("L1").Resize(UBound(BayRay), UBound(BayRay, 2)).value = BayRay
End Sub
Loop Through a Row of a 2D Array
Option Explicit
Sub LoopThroughRow()
Const RowIndex As Long = 2
Const Criteria As Double = 3
Const MinNum As Long = 1
Const MaxNum As Long = 5
' Populate with random integers.
Dim Data As Variant: ReDim Data(1 To 5, 1 To 5)
Dim r As Long, c As Long
For r = LBound(Data, 1) To UBound(Data, 1)
For c = LBound(Data, 2) To UBound(Data, 2)
Data(r, c) = Int((MaxNum - MinNum + 1) * Rnd + MinNum)
Next c
Next r
' Write criteria row.
For c = LBound(Data, 2) To UBound(Data, 2)
If Data(RowIndex, c) > Criteria Then
Data(RowIndex + 1, c) = "Yes"
Else
Data(RowIndex + 1, c) = "No"
End If
Next c
' Print result.
Debug.Print "Column", "Row " & RowIndex, "Row " & RowIndex + 1
For c = LBound(Data, 2) To UBound(Data, 2)
Debug.Print c, Data(RowIndex, c), Data(RowIndex + 1, c)
Next c
End Sub
I'm trying to have a for loop that takes my weeks and then another for loop that looks at my product to calculate the sales for this year, last year and then the difference. I am getting 0's for all of the weeks except for the current week, any idea what is wrong with my code? Thanks
Sub Weekly_Recap()
Dim h, d As Worksheet
Dim myarray(), answers() As Variant
'Dim week, datarange As Range
Dim D1, i As Long
Set h = Worksheets("Helper")
Set d = Worksheets("Data")
myarray = d.Range("P2:P51")
D1 = UBound(myarray, 1)
ReDim answers(1 To D1, 1 To 3)
For i = 1 To D1
If myarray(i, 1) <= h.Range("A1") Then
For j = 1 To 17
answers(i, 1) = Application.WorksheetFunction.SumIfs(d.Range("G:G"), d.Range("B:B"), myarray(i, 1), d.Range("F:F"), h.Cells(j, 4))
answers(i, 2) = Application.WorksheetFunction.SumIfs(d.Range("H:H"), d.Range("B:B"), myarray(i, 1), d.Range("F:F"), h.Cells(j, 4))
answers(i, 3) = (answers(i, 1) - answers(1, 2)) / answers(i, 2)
If h.Cells(j, 4) = "FLAVORED/FUNCTIONAL WATER" Then
h.Range(h.Range("F2"), h.Range("F2").Offset(D1, 2)).Value = answers
ElseIf h.Cells(j, 4) = "SALTY BAGGED/CANISTER SNACKS" Then
h.Range(h.Range("K2"), h.Range("K2").Offset(D1, 2)).Value = answers
End If
Next j
End If
Next i
End Sub
Kinda hard to understand what you are trying to do. When I tried to run your code in debug mode I noticed that line with offset is overwriting data in previous lines, and that is where you are getting all 0's. In lets say loop i = 1 and j = 1 you get some value, but in i = 1 and j = 2 you don't (sumif returns 0) and then you overwrite it in variable answers and then paste it in worksheet, at the end only last one has not been overwritten.
You need swap the loops to calculate all the weeks for each product in turn otherwise the figure for week1/product1 will be overwritten by week1/product2 then week1/product3 etc.
Option Explicit
Sub Sales()
Dim arWeeks, NoOfWeeks As Long, iLastWeek As Long
Dim rngSales1 As Range, rngSales2 As Range
Dim rngWeek As Range, rngProduct As Range
Dim iWeek As Long, sProduct As String
Dim h As Worksheet, d As Worksheet
Dim j As Long, i As Long
Set h = Worksheets("Helper")
Set d = Worksheets("Data")
Set rngSales1 = d.Range("G:G")
Set rngSales2 = d.Range("H:H")
Set rngWeek = d.Range("B:B")
Set rngProduct = d.Range("F:F")
arWeeks = d.Range("P2:P51")
iLastWeek = h.Range("A1").Value
NoOfWeeks = UBound(arWeeks)
ReDim answers(1 To NoOfWeeks, 1 To 3) 'yr1,yr2,diff
For j = 1 To 17 ' products
sProduct = h.Cells(j, 4)
For i = 1 To NoOfWeeks ' weeks
iWeek = arWeeks(i, 1)
If iWeek <= iLastWeek Then
With Application.WorksheetFunction
answers(i, 1) = .SumIfs(rngSales1, rngWeek, iWeek, rngProduct, sProduct)
answers(i, 2) = .SumIfs(rngSales2, rngWeek, iWeek, rngProduct, sProduct)
End With
If answers(i, 2) <> 0 Then
answers(i, 3) = (answers(i, 1) - answers(1, 2)) / answers(i, 2)
End If
End If
Next i
If sProduct = "FLAVORED/FUNCTIONAL WATER" Then
h.Range("F2").Resize(NoOfWeeks, 3).Value = answers
ElseIf sProduct = "SALTY BAGGED/CANISTER SNACKS" Then
h.Range("k2").Resize(NoOfWeeks, 3).Value = answers
End If
Next j
MsgBox "Done ", vbInformation
End Sub
I have a code that matches a cell value in Column C on Sheet1 to a pivot table on Sheet3 and then copies certain columns over.
Code will check how many entries there are on Sheet1 that need to be checked
Loop 2: For every value in Column C/Sheet1 with a match in Column A on Sheet 2 it will then copy over the corresponding data from Column B,C,D,E.
Since there are multiple matches possible by value/Sheet I am limiting the data pull to three matches (three loops in the code). To achieve that I am increasing i +1 or i+2 to get the next row in the pivot table.
The table on Sheet 2 is sometimes 10,000+ rows and excel crashes.
Does anyone have an idea how to speed up the loop codes (Loop2,3,4 are the same) to make it less work intensive e.g. array possibly? They are causing the lock up since I think the code keeps running up and down column A.
Set sheet3 = Sheets("OrbitPivotTable")
CellChanged = Sheet1.Range("A1").Value + 1
LastRow = sheet3.Cells(Rows.Count, "A").End(xlUp).Row
LastData = Sheet1.Cells(Rows.Count, "C").End(xlUp).Row
'Loop1
For i = 1 To LastRow
If Sheet1.Range("C" & CellChanged).Value = "" Then GoTo Nextstep2
If Sheet1.Range("C" & CellChanged).Value = sheet3.Range("A" & i) Then
Sheet1.Range("H" & CellChanged).Value = sheet3.Range("B" & i).Value 'Customer
Sheet1.Range("I" & CellChanged).Value = sheet3.Range("C" & i).Value 'Rate Val start
Sheet1.Range("J" & CellChanged).Value = sheet3.Range("D" & i).Value 'ATA All in
Sheet1.Range("K" & CellChanged).Value = sheet3.Range("E" & i).Value 'Special Remarks
Found = True
End If
If Found = True Or i = LastRow Then
If CellChanged = LastData Then
Exit For
End If
If Found = True Then
Found = False
Nextstep2:
CellChanged = CellChanged + 1
End If
i = 0
End If
Next i
'Loop2
etc....
Excel File
I might have misunderstood the process in the file you shared, but this should be faster (and much less code overall).
I put the pivot table lookup in a loop, switched to Match(), and reduced the number of read/writes using arrays where possible.
EDITED to fix an embarrassing bug where I forgot to adjust the Match() result m to account for the starting row of the range I run match() against...
Sub HB_IPT_Rate_Check()
Dim wsReport As Worksheet, wsCPK As Worksheet, wsOrbitPivot As Worksheet
Dim c As Range, rwReport As Range, lastPivotRow As Long
Dim ata, m, numMatches As Long, matchFrom As Long, matchRow As Long
Set wsReport = ThisWorkbook.Worksheets("Comparison Report")
Set wsCPK = ThisWorkbook.Worksheets("CPK")
Set wsOrbitPivot = ThisWorkbook.Worksheets("OrbitPivotTable")
'loop over the rows in the report sheet
For Each c In wsReport.Range("C3", wsReport.Cells(Rows.Count, "C").End(xlUp)).Cells
ata = c.Value 'read this once....
Set rwReport = c.EntireRow
'1st Database Match "CPK"
m = Application.Match(ata, wsCPK.Columns("A"), 0)
If Not IsError(m) Then
With wsCPK.Rows(m)
rwReport.Columns("D").Resize(1, 4).Value = _
Array(.Columns("B").Value, .Columns("C").Value, _
.Columns("F").Value, .Columns("H").Value)
'Sum of HB CWGT (KG),Sum of MB CWGT (KG),Achiev CPK,Density
End With
Else
'no match...
End If
'2nd Database Match "Orbit"
lastPivotRow = wsOrbitPivot.Cells(Rows.Count, "A").End(xlUp).Row
numMatches = 0 'reset match count
matchFrom = 2
m = Application.Match(ata, wsOrbitPivot.Range("A" & matchFrom & ":A" & lastPivotRow), 0)
'keep going while we still have a match and we've not reached the max result count
Do While Not IsError(m) And numMatches < 3
numMatches = numMatches + 1
matchRow = matchFrom + (m - 1) 'adjust the matched row index according to where we started looking...
'sanity check
Debug.Print "Matched " & ata & " on row " & matchRow
rwReport.Columns("H").Offset(0, (numMatches - 1) * 4).Resize(1, 4).Value = _
wsOrbitPivot.Cells(matchRow, "B").Resize(1, 4).Value
'find the next match if any, starting below the last match
matchFrom = matchRow + 1
m = Application.Match(ata, wsOrbitPivot.Range("A" & matchFrom & ":A" & lastPivotRow), 0)
Loop
Next c 'next report row
End Sub
Use Dictionary to set row and column number.
Data is assigned to fit rows and columns in a virtual array.
Sub test()
Dim Ws(1 To 4) As Worksheet
Dim DicR As Object ' Dictionary
Dim DicC As Object ' Dictionary
Dim vDB, arr()
Dim s As String
Dim i As Long, n As Long, j As Integer
Dim r As Long, c As Integer
Set Ws(1) = Sheets("Comparison Report")
Set Ws(2) = Sheets("CPK")
Set Ws(3) = Sheets("OrbitPivotTable")
Set Ws(4) = Sheets("Orbit")
'Row index dictionary
Set DicR = CreateObject("Scripting.Dictionary") 'New Scripting.Dictionary
'Column index dictionary
Set DicC = CreateObject("Scripting.Dictionary") ' New Scripting.Dictionary
vDB = Ws(1).UsedRange
For i = 3 To UBound(vDB, 1)
s = vDB(i, 3)
If s <> "" Then
If DicR.Exists(s) Then
'DicC(s) = DicC(s) + 1
Else
n = n + 1
DicR.Add s, n 'row index
DicC.Add s, 0 'column index
End If
End If
Next i
'Create an array of virtual tables based on the number of dictionaries.
'Since the number of columns cannot be predicted, a specific number of 1000 was entered.
'in my test, number 100 is too small
ReDim arr(1 To DicR.Count, 1 To 1000)
For j = 2 To 4
vDB = Ws(j).Range("a1").CurrentRegion
For i = 2 To UBound(vDB, 1)
s = vDB(i, 1)
If DicR.Exists(s) Then
r = DicR(s)
c = DicC(s) * 4 + 1
DicC(s) = DicC(s) + 1
arr(r, c) = vDB(i, 2)
arr(r, c + 1) = vDB(i, 3)
arr(r, c + 2) = vDB(i, 4)
arr(r, c + 3) = vDB(i, 5)
End If
Next i
Next j
With Ws(1)
.Range("d3").Resize(UBound(arr, 1), UBound(arr, 2)) = arr
End With
End Sub
Result image
This should be easy and I think I am almost there. I would like to count how many times an entry repeats itself within a certain array. The array will be populated from a range. Eventually if the number of the count is more than 4, I would like to insert "Excess", otherwise if less than 4, I would like to insert "Insufficient", else is "complete". Unfortunately, even though I have learnt to do these calculations without using Arrays, I find some difficulties when switching to Arrays.
How the code should look like
Sub test()
Dim MyArray() As Variant, Countarrays() As Variant, Result() As Variant
Dim r As Range
Dim rows As Integer
Worksheets("Sheet1").Activate
Set r = Range("B2", Range("B1").End(xlDown))
MyArray = Range("B2", Range("B1").End(xlDown))
rows = Range("B2", Range("B1").End(xlDown)).Count
For i = 0 To rows
For j = 0 To rows
Countarrays(i, 1) = WorksheetFunction.CountIf(r, MyArray(i))
If (Countarrays(i, 1).value) > 4 Then Result(j, 1) = "Excess"
ElseIf (Countarrays(i, 1).value) < 4 Then Result(j, 1) = "Insufficient"
ElseIf (Countarrays(i, 1).value) = 4 Then Result(j, 1) = "Complete"
Next j
Next i
End Sub
This should do the trick:
Option Explicit
Sub Test()
Dim MyArray, DictDuplicates As New Scripting.Dictionary, i As Long
With ThisWorkbook.Sheets("Sheet1") 'change if needed
MyArray = .Range(.Cells(2, 1), .Cells(2, 2).End(xlDown))
For i = LBound(MyArray) To UBound(MyArray) 'loop to store all the items and how many times do they repeat
If Not DictDuplicates.Exists(MyArray(i, 2)) Then 'if doesn't exists will store it
DictDuplicates.Add MyArray(i, 2), 1
Else 'if it does exists will increment its item value
DictDuplicates(MyArray(i, 2)) = DictDuplicates(MyArray(i, 2)) + 1
End If
Next i
For i = LBound(MyArray) To UBound(MyArray) 'loop to give back the result
Select Case DictDuplicates(MyArray(i, 2))
Case Is > 4
MyArray(i, 1) = "Excess"
Case Is = 4
MyArray(i, 1) = "Complete"
Case Is < 4
MyArray(i, 1) = "Insufficient"
End Select
Next i
.Range(.Cells(2, 1), .Cells(2, 2).End(xlDown)) = MyArray
End With
End Sub
Note that for the DictDuplicates to work, you need to check the Microsoft Scripting Runtime library.
The code works when I bite off a couple hundred rows at a time, but always hangs somewhere in the middle when I try to run it on 10,000.
What the code does: Looks for duplicate entries in column A, adds the values in columns c, d and e between the two rows, then deletes the original row.
Can anybody think of a more stable way to do this, or point me towards why it might be locking up?
Sub combineDelete ()
Const TEST_COLUMN As String = "A"
Dim i As Long
Dim iLastRow As Long
With ActiveSheet
iLastRow = .Cells(.Rows.Count, TEST_COLUMN).End(xlUp).Row
For i = iLastRow To 2 Step -1
If Cells(i, 1) = Cells(i - 1, 1) Then
s = Cells(i, 3).Value
t = Cells(i - 1, 3).Value
Cells(i - 1, 3) = s + t
u = Cells(i, 4).Value
v = Cells(i - 1, 4).Value
Cells(i - 1, 4) = u + v
w = Cells(i, 5).Value
y = Cells(i - 1, 5).Value
Cells(i - 1, 5) = w + y
Cells(i, 1).EntireRow.Delete
End If
Next i
End With
End Sub
Edit: Here's a link to a sample subset of the data.
Post-edit: Every one of these ideas is effective. Ron Rosenberg's solution below manages to handle it orders of magnitude faster than any solution I tinkered with. Thanks!
Start with this and let us know how things are going afterwards:
Option Explicit
Sub combineDelete()
Const TEST_COLUMN As String = "A"
Dim i As Long
Dim iLastRow As Long
Dim s As Double, t As Double, u As Double
Dim v As Double, w As Double, y As Double
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
With ActiveSheet
iLastRow = .Cells(.Rows.Count, TEST_COLUMN).End(xlUp).Row
For i = iLastRow To 2 Step -1
If .Cells(i, 1).Value2 = .Cells(i - 1, 1).Value2 Then
s = .Cells(i, 3).Value2
t = .Cells(i - 1, 3).Value2
.Cells(i - 1, 3).Value2 = s + t
u = .Cells(i, 4).Value2
v = .Cells(i - 1, 4).Value2
.Cells(i - 1, 4).Value2 = u + v
w = .Cells(i, 5).Value2
y = .Cells(i - 1, 5).Value2
.Cells(i - 1, 5).Value2 = w + y
.Rows(i).EntireRow.Delete
End If
Next i
End With
With Application
.EnableEvents = True
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
Notes:
Disable screenupdating, calculations and events
Use .Value2 instead of .Value
Explicit coding
Missing references to ActiveSheet added by adding dots .
Dim all variables to avoid variants
Here is a routine that should run quite rapidly. You will note near the top of the code where to change the source and results worksheets if you want.
The work is done within VBA arrays, which will be much faster than working on the worksheet.
I create a User defined object whose properties are the contents of the TestColumn; the Maximum amount in Column B; and an array of the Sum of Columns C, D and E.
These are placed into a Collection object with the Key being the TestColumn. If there is a duplicate, the Collection object will return a 457 error, which we test for and use to combine the rows.
Finally, we write the collection object back to an array, and write that array to the worksheet.
You will use both a Class Module and a Regular Module
The original data does not need to be sorted, but you can if you want, either before or after running this macro.
Enjoy.
Class Module
Be sure to rename this module cCombo after inserting it
Rename this module **cCombo**
Option Explicit
Private pTestColumn As String
Private pMaxColumn As Double
Private pSumColumns(3 To 5) As Variant
Public Property Get TestColumn() As String
TestColumn = pTestColumn
End Property
Public Property Let TestColumn(Value As String)
pTestColumn = Value
End Property
Public Property Get MaxColumn() As Double
MaxColumn = pMaxColumn
End Property
Public Property Let MaxColumn(Value As Double)
pMaxColumn = IIf(pMaxColumn > Value, pMaxColumn, Value)
End Property
Public Property Get SumColumns() As Variant
SumColumns = pSumColumns
End Property
Public Property Let SumColumns(Value As Variant)
Dim I As Long
For I = LBound(Value) To UBound(Value)
pSumColumns(I) = pSumColumns(I) + Value(I)
Next I
End Property
Regular Module
Option Explicit
Sub combineDelete()
Const TEST_COLUMN As String = "A"
Dim vSrc As Variant, vRes As Variant, rRes As Range
Dim wsSrc As Worksheet, wsRes As Worksheet
Dim cC As cCombo, colC As Collection
Dim I As Long, J As Long, V As Variant, S As String
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet2") 'could be same sheet if you want to overwrite
Set rRes = wsRes.Cells(2, 1)
'Get original data
With wsSrc
vSrc = Range(.Cells(2, TEST_COLUMN), .Cells(.Rows.Count, TEST_COLUMN).End(xlUp)).Resize(columnsize:=5)
End With
ReDim V(3 To UBound(vSrc, 2)) 'for storing rows
'Collect the data, eliminating duplicates
Set colC = New Collection
On Error Resume Next
For I = 1 To UBound(vSrc, 1)
Set cC = New cCombo
With cC
.TestColumn = vSrc(I, 1)
.MaxColumn = vSrc(I, 2)
For J = 3 To UBound(vSrc, 2)
V(J) = vSrc(I, J)
Next J
.SumColumns = V
colC.Add Item:=cC, Key:=.TestColumn
Select Case Err.Number
Case 457
Err.Clear
colC(.TestColumn).MaxColumn = .MaxColumn
colC(.TestColumn).SumColumns = .SumColumns
Case Is <> 0
Debug.Print Err.Number, Err.Description
Stop
End Select
End With
Next I
On Error GoTo 0
'Create results array
ReDim vRes(1 To colC.Count, 1 To 5)
For I = 1 To colC.Count
With colC(I)
vRes(I, 1) = .TestColumn
vRes(I, 2) = .MaxColumn
V = .SumColumns
For J = LBound(V) To UBound(V)
vRes(I, J) = V(J)
Next J
End With
Next I
'Write the results
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
.EntireColumn.ColumnWidth = 5
End With
End Sub
Working with ~10K rows would benefit immensely from a variant array but you can also make significant improvements by deleting all of the rows at once. While you could gather a Union of the rows to delete, a Range.RemoveDuplicates method is also appropriate in this case.
It is unclear on whether your data is sorted on a primary key of column A. Your current code depends upon this but I've changed the criteria check to the Excel Application object's MATCH function to accommodate unsorted data.
Your code appears to avoid text column header labels in row 1. I've used the Range.CurrentRegion property to localize the cells to be processed.
Sub combineDelete()
Const TEST_COLUMN As String = "A"
Dim i As Long, mtch As Long
'appTGGL bTGGL:=False 'uncomment this line once you have completed debugging
With ActiveSheet
With .Cells(1, 1).CurrentRegion
For i = .Rows.Count To 2 Step -1
mtch = Application.Match(.Cells(i, 1).Value, .Columns(1), 0)
If mtch < i Then
.Cells(mtch, 3) = Application.Sum(.Cells(mtch, 3), .Cells(i, 3))
.Cells(mtch, 4) = Application.Sum(.Cells(mtch, 4), .Cells(i, 4))
.Cells(mtch, 5) = Application.Sum(.Cells(mtch, 5), .Cells(i, 5))
End If
Next i
.RemoveDuplicates Columns:=1, Header:=xlYes
End With
End With
appTGGL
End Sub
Public Sub appTGGL(Optional bTGGL As Boolean = True)
With Application
.ScreenUpdating = bTGGL
.EnableEvents = bTGGL
.DisplayAlerts = bTGGL
.Calculation = IIf(bTGGL, xlCalculationAutomatic, xlCalculationManual)
.StatusBar = vbNullString
End With
Debug.Print Timer
End Sub
The use of Application.Sum(..., ...) is a trifle slower than straight addition but it has the benefit of providing error control over text values. This may or may not be a desired behavior; i.e. you might want to know when you are trying to add text to a number instead of skipping over it.
There were many places inside your With ... End With statement where you used Cells(i, 3) and not .Cells(i, 3) (note the prefix . ). If you are going to take the time to reference the Range.Parent property (and you should always do so!) then it seems a shame not to use it.
I've included a reusable 'helper' sub that 'turns off' many application environment states but left it commented. Uncomment it once you havew completed debugging for additional speed and stability.
Addendum for lookup strings with length > 255
Sub combineDelete()
Dim i As Long, mtch As Long
Dim vCOLAs As Variant, dCOLAs As Object
'appTGGL bTGGL:=False 'uncomment this line once you have completed debugging
Set dCOLAs = CreateObject("Scripting.Dictionary")
dCOLAs.CompareMode = vbTextCompare
With ActiveSheet
With .Cells(1, 1).CurrentRegion
'strings in column A may exceed 255 chars; build array and and a dictionary from array
vCOLAs = .Resize(.Rows.Count, 1).Value2
For i = UBound(vCOLAs, 1) To LBound(vCOLAs, 1) Step -1
'fast overwrite method
dCOLAs.Item(vCOLAs(i, 1)) = i
Next i
For i = .Rows.Count To 2 Step -1
mtch = dCOLAs.Item(vCOLAs(i, 1))
If mtch < i Then
.Cells(mtch, 3) = Application.Sum(.Cells(mtch, 3), .Cells(i, 3))
.Cells(mtch, 4) = Application.Sum(.Cells(mtch, 4), .Cells(i, 4))
.Cells(mtch, 5) = Application.Sum(.Cells(mtch, 5), .Cells(i, 5))
End If
Next i
.RemoveDuplicates Columns:=1, Header:=xlYes
End With
End With
Erase vCOLAs
dCOLAs.RemoveAll: Set dCOLAs = Nothing
appTGGL
End Sub
A dictionary object provides lightning fast lookups due to its unique keys. Since these are a variant type, there is no 255 character limit.