使用 Sumifs() 的更快方法
Faster way of using Sumifs()
我有一个每周任务,我需要更新一份报告(目前刚刚超过 50K 行),每周增加大约 500 行。手动添加新数据后,我运行下面的代码做了一个Sumifs()
来汇总数据。
数据结构是:A 到 C 列是条件列(数字-字母-数字),D 列具有要求和的数量(整数)。数据是连续的。我的宏将 Sumifs()
公式放入 E 列 - 覆盖那里的内容。
我的问题是:这个任务能不能快点完成?目前 运行 宏只花了我一分多钟,但随着数据的增长,这个时间会变长。
这个网站上有很多关于使用数组更快地完成任务的内容,但是 none 的示例对我来说很有意义,如果可能我宁愿不使用它们。
Sub MySumIfs()
Dim LastRow As Long
LastRow = Sheet1.Range("A1").End(xlDown).Row
With Sheet1.Range("E2:E" & LastRow)
.FormulaR1C1 = "=sumifs(R2C4:R" & LastRow & "C4, R2C1:R" & LastRow & "C1, RC1, R2C2:R" & LastRow & "C2, RC2, R2C3:R" & LastRow & "C3, RC3)"
.Value = .Value
End With
End Sub
这是另一种方式:
编辑 - 更新为将“averageifs”和“sumifs”添加到我最初的(错误的)“countifs”版本...
Sub SetupDummyData()
Const NUM As Long = 100001
Range("A1:E1").Value = Array("A_Header", "B_Header", "C_Header", "Value", "ResultHere")
Range("A2:A" & NUM).Formula = "=""A#"" & round(RAND()*10,0)"
Range("B2:B" & NUM).Formula = "=""B#"" & round(RAND()*10,0)"
Range("C2:C" & NUM).Formula = "=""C#"" & round(RAND()*10,0)"
Range("D2:D" & NUM).Formula = "=round(RAND()*100,1)"
Range("A2:D" & NUM).Value = Range("A2:D" & NUM).Value
End Sub
Sub Tester()
Dim arr, ws, rng As Range, keyCols, valueCol As Long, destCol As Long, i As Long, frm As String, sep As String
Dim t, dict, arrOut(), arrValues(), v, tmp, n As Long
keyCols = Array(1, 2, 3) 'these columns form the composite key
valueCol = 4 'column with values (for sum)
destCol = 5 'destination for calculated values
t = Timer
Set ws = ActiveSheet
Set rng = ws.Range("A1").CurrentRegion
n = rng.Rows.Count - 1
Set rng = rng.Offset(1, 0).Resize(n) 'exclude headers
'build the formula to create the row "key"
For i = 0 To UBound(keyCols)
frm = frm & sep & rng.Columns(keyCols(i)).Address
sep = "&""|""&"
Next i
arr = ws.Evaluate(frm) 'get an array of composite keys by evaluating the formula
arrValues = rng.Columns(valueCol).Value 'values to be summed
ReDim arrOut(1 To n, 1 To 1) 'this is for the results
Set dict = CreateObject("scripting.dictionary")
'first loop over the array counts the keys
For i = 1 To n
v = arr(i, 1)
If Not dict.exists(v) Then dict(v) = Array(0, 0) 'count, sum
tmp = dict(v) 'can't modify an array stored in a dictionary - pull it out first
tmp(0) = tmp(0) + 1 'increment count
tmp(1) = tmp(1) + arrValues(i, 1) 'increment sum
dict(v) = tmp 'return the modified array
Next i
'second loop populates the output array from the dictionary
For i = 1 To n
arrOut(i, 1) = dict(arr(i, 1))(1) 'sumifs
'arrOut(i, 1) = dict(arr(i, 1))(0) 'countifs
'arrOut(i, 1) = dict(arr(i, 1))(1) / dict(arr(i, 1))(0) 'averageifs
Next i
'populate the results
rng.Columns(destCol).Value = arrOut
Debug.Print "Checked " & n & " rows in " & Timer - t & " secs"
End Sub
我有一个每周任务,我需要更新一份报告(目前刚刚超过 50K 行),每周增加大约 500 行。手动添加新数据后,我运行下面的代码做了一个Sumifs()
来汇总数据。
数据结构是:A 到 C 列是条件列(数字-字母-数字),D 列具有要求和的数量(整数)。数据是连续的。我的宏将 Sumifs()
公式放入 E 列 - 覆盖那里的内容。
我的问题是:这个任务能不能快点完成?目前 运行 宏只花了我一分多钟,但随着数据的增长,这个时间会变长。
这个网站上有很多关于使用数组更快地完成任务的内容,但是 none 的示例对我来说很有意义,如果可能我宁愿不使用它们。
Sub MySumIfs()
Dim LastRow As Long
LastRow = Sheet1.Range("A1").End(xlDown).Row
With Sheet1.Range("E2:E" & LastRow)
.FormulaR1C1 = "=sumifs(R2C4:R" & LastRow & "C4, R2C1:R" & LastRow & "C1, RC1, R2C2:R" & LastRow & "C2, RC2, R2C3:R" & LastRow & "C3, RC3)"
.Value = .Value
End With
End Sub
这是另一种方式:
编辑 - 更新为将“averageifs”和“sumifs”添加到我最初的(错误的)“countifs”版本...
Sub SetupDummyData()
Const NUM As Long = 100001
Range("A1:E1").Value = Array("A_Header", "B_Header", "C_Header", "Value", "ResultHere")
Range("A2:A" & NUM).Formula = "=""A#"" & round(RAND()*10,0)"
Range("B2:B" & NUM).Formula = "=""B#"" & round(RAND()*10,0)"
Range("C2:C" & NUM).Formula = "=""C#"" & round(RAND()*10,0)"
Range("D2:D" & NUM).Formula = "=round(RAND()*100,1)"
Range("A2:D" & NUM).Value = Range("A2:D" & NUM).Value
End Sub
Sub Tester()
Dim arr, ws, rng As Range, keyCols, valueCol As Long, destCol As Long, i As Long, frm As String, sep As String
Dim t, dict, arrOut(), arrValues(), v, tmp, n As Long
keyCols = Array(1, 2, 3) 'these columns form the composite key
valueCol = 4 'column with values (for sum)
destCol = 5 'destination for calculated values
t = Timer
Set ws = ActiveSheet
Set rng = ws.Range("A1").CurrentRegion
n = rng.Rows.Count - 1
Set rng = rng.Offset(1, 0).Resize(n) 'exclude headers
'build the formula to create the row "key"
For i = 0 To UBound(keyCols)
frm = frm & sep & rng.Columns(keyCols(i)).Address
sep = "&""|""&"
Next i
arr = ws.Evaluate(frm) 'get an array of composite keys by evaluating the formula
arrValues = rng.Columns(valueCol).Value 'values to be summed
ReDim arrOut(1 To n, 1 To 1) 'this is for the results
Set dict = CreateObject("scripting.dictionary")
'first loop over the array counts the keys
For i = 1 To n
v = arr(i, 1)
If Not dict.exists(v) Then dict(v) = Array(0, 0) 'count, sum
tmp = dict(v) 'can't modify an array stored in a dictionary - pull it out first
tmp(0) = tmp(0) + 1 'increment count
tmp(1) = tmp(1) + arrValues(i, 1) 'increment sum
dict(v) = tmp 'return the modified array
Next i
'second loop populates the output array from the dictionary
For i = 1 To n
arrOut(i, 1) = dict(arr(i, 1))(1) 'sumifs
'arrOut(i, 1) = dict(arr(i, 1))(0) 'countifs
'arrOut(i, 1) = dict(arr(i, 1))(1) / dict(arr(i, 1))(0) 'averageifs
Next i
'populate the results
rng.Columns(destCol).Value = arrOut
Debug.Print "Checked " & n & " rows in " & Timer - t & " secs"
End Sub