我想根据几个条件在数组中总结一个列。
如果数据在Excel中,我会使用=SUMIFS
公式。
我所拥有的二维数组中的样本数据集是:
ID1 ID2 ID3 Value
0 1 1 4
0 2 2 5
1 3 2 6
0 1 1 3
0 1 0 2
我想根据以下条件对值列求和:
ID1=0
ID2=1
ID3=1
因此第1行和第4行符合此标准,因此答案为7(4 + 3)
我将如何在VBA中构建它。
请注意,ID可能是无限的,也可能是字符串,因此我无法在循环中设置ID=0
。
答案 0 :(得分:5)
只是对速度的一个小警告!
我认为问题是 2D数组而不是 excel.range ,因为excel范围上的循环非常懒散(仅当您有一个循环时才有效)很多数据,但我敢打赌,如果您计划使用VBA宏,这是通常的情况;-))
之前我发现有一些链接报告了这个问题,我已经受到了范围的缓慢的影响(对于10000个单元格的示例,一个用户使用2D数组报告9,7seg与0,16 seg !!)。链接如下。我的建议是始终使用2D数组,简单,干净,快速!
在以下位置查看更多性能测试:
因此,如果你想处理大量数据,Jakub的回复代码应该稍微改变一下,以获得 2D数组的 power :
Public Function sumIfMultipleConditionsMet2(rng As Range, ParamArray conditions() As Variant) As Double
Dim conditionCount As Long: conditionCount = UBound(conditions) + 1
Dim summedColumnIndex As Long: summedColumnIndex = conditionCount + 1
Dim currentRow As Range
Dim result As Double: result = 0 'Changed from Long to Double
Dim i As Long
If rng.Columns.Count <> conditionCount + 1 Then
Err.Raise 17, , "Invalid range passed"
End If
Dim conditionsMet As Boolean
'USING AN ARRAY INSTEAD OF A RANGE
Dim arr As Variant
arr = rng.Value 'Copy the range to an array
Dim r As Long
For r = LBound(arr, 1) To UBound(arr, 1) 'OLD: For Each currentRow In rng.Rows
conditionsMet = True
For i = LBound(conditions) To UBound(conditions)
' cells collection is indexed from 1, the array from 0
' OLD: conditionsMet = conditionsMet And (currentRow.Cells(1, i + 1).Value = conditions(i))
conditionsMet = conditionsMet And (arr(r, i + 1) = conditions(i))
Next i
If conditionsMet Then
'OLD: result = result + currentRow.Cells(1, summedColumnIndex).Value
result = result + arr(r, summedColumnIndex)
End If
Next r
sumIfMultipleConditionsMet2 = result
End Function
使用方式与Jakub在回复中显示的方式相同:
debug.Print sumIfMultipleConditionsMet2(Range("A1:D50000"), 0, 1, 1)
希望你喜欢它!
此致 安德烈
PS:如果你想更进一步,这里有更多关于excel的速度提示。希望你喜欢!
答案 1 :(得分:3)
您可以使用paramArray功能获得sumif函数的更通用版本。例如:
Public Function sumIfMultipleConditionsMet(rng As range, ParamArray conditions() As Variant) As Long
Dim conditionCount As Long: conditionCount = UBound(conditions) + 1
Dim summedColumnIndex As Long: summedColumnIndex = conditionCount + 1
Dim currentRow As range
Dim result As Long: result = 0
Dim i As Long
If rng.Columns.Count <> conditionCount + 1 Then
Err.Raise 17, , "Invalid range passed"
End If
Dim conditionsMet As Boolean
For Each currentRow In rng.Rows
conditionsMet = True
For i = LBound(conditions) To UBound(conditions)
' cells collection is indexed from 1, the array from 0
conditionsMet = conditionsMet And (currentRow.Cells(1, i + 1).Value = conditions(i))
Next i
If conditionsMet Then
result = result + currentRow.Cells(1, summedColumnIndex).Value
End If
Next
sumIfMultipleConditionsMet = result
End Function
然后你可以像这样使用它:
debug.Print sumIfMultipleConditionsMet(Range("A1:D5"), 0, 1, 1)
答案 2 :(得分:1)
好的,你说你有一个2D数组(不是excel范围),但没有具体说明数组的确切形状。所以我必须假设您的2D数组是“arr”,其形式为:arr(c,r) as variant
,其中r
用于访问行,c
用于列(1代表“ ID1“,2表示”ID2“,3表示”ID3“,4表示”值“)。 (如果您不遵循这个想法,请参阅“注释1”和“注释2”以进一步说明。)
然后你只需做一个小循环:
tot = 0
For i = LBound(arr, 2) To UBound(arr, 2) ' The "2" in the second paramenter is
' for getting the lower and upper bound
' of the "2nd" dimention of the array
If arr(1, i) = A And arr(2, i) = B And arr(3, i) = C Then
tot = tot + arr(4, i)
End If
Next i
tot
变量将包含您尝试计算的总数。容易??
如果要在函数中扭曲上一个,可以使用:
Public Function SumIfMyArray(arr As Variant, A As Variant, _
B As Variant, C As Variant) As Double
Dim i as Long
Dim tot As Double
tot = 0
For i = LBound(arr, 2) To UBound(arr, 2)
If arr(1, i) = A And arr(2, i) = B And arr(3, i) = C Then
tot = tot + arr(4, i) 'Adding the filtered value
End If
Next i
SumIfMyArray = tot 'Returning the calculated sum
End Function
使用它:Debug.Print SumIfMyArray(YouArr, 1, 1, 1)
。希望这会有所帮助。
更复杂(但更灵活):
现在,如果你想拥有一个非常通用的函数来支持不同的标准并同时灵活地使用列,你可以使用下面的代码(注意,我在其他回复中使用ParamArray) 。实际上该函数可以使用arr(c,r)
形式的数组(该数组形式更容易使用redim
指令添加更多行)而第二种形式使用arr(r,c)
形式(此数组形式更简单)如果使用arr=range("A1:D5")
复制excel范围。
Private Function SumIfConditionsMetArray(ColToAdd As Long, Arr As Variant, _
TypeArrayIsRC As Boolean, _
ParamArray Criteria() As Variant) As Double
' Returns: The sum of values from a column where
' the row match the criteria.
' Parameters:
' 1) Arr: An array in the form of arr(row,col) (
' (like the array passed by an excel range)
' 2) ColToAdd: Index of column you want to add.
' 3) TypeArrayIsRC: 'True' if the array passed if in the
' form of arr(Row,Column) or 'False' if
' the array is in the form arr(Column,Row).
' Note that passing an range as
' arr=range("A1:B3").value , then "true"
' should be used!
' 4) Criteria: a list of criteria you want to use for
' filtering, if you want to skip a column
' from the criteria use "Null" in the
' parameter list.
'
' Example: Debug.Print SumIfConditionsMetArray(4, data, true, 9, null, 5)
' (It means: sum column 4 of data where 1st column
' match "9" and 3rd column match "5".
' The 2nd column was skipped because of null)
Dim tot As Double
Dim CountCol As Long
Dim r As Long, c As Long
Dim conditionsMet As Boolean
Dim cExtra As Long
Dim DimRow As Long, DimCol As Long
If TypeArrayIsRC Then
DimRow = 1: DimCol = 2
Else
DimRow = 2: DimCol = 1
End If
'Some checking...
If ColToAdd < LBound(Arr, DimCol) Or ColToAdd > UBound(Arr, DimCol) Then
Err.Raise vbError + 9, , "Error in function SumIfConditionsMetArray. ColToAdd is out of the range."
End If
'Correction in case of different array bases..
cExtra = LBound(Arr, DimCol) - LBound(Criteria) 'In case the lower bound were different...
'Limit the last column to check
CountCol = UBound(Criteria)
If CountCol > UBound(Arr, DimCol) - cExtra Then
'Not raising an error, just skip out the extra parameters!
'(Put err.raise if you want an error instead)
CountCol = UBound(Arr, DimCol) - cExtra
End If
On Error GoTo errInFunction
'''' LOOP ''''
Dim A As Long
Dim B As Long
tot = 0
For r = LBound(Arr, DimRow) To UBound(Arr, DimRow)
If TypeArrayIsRC Then
A = r
Else
B = r
End If
conditionsMet = True
For c = LBound(Criteria) To CountCol
If Not IsNull(Criteria(c)) Then
If TypeArrayIsRC Then
B = c + cExtra
Else
A = c + cExtra
End If
If Arr(A, B) <> Criteria(c) Then
conditionsMet = False 'Creteria not met
End If
End If
Next c
If TypeArrayIsRC Then
B = ColToAdd
Else
A = ColToAdd
End If
If conditionsMet Then
tot = tot + Arr(A, B) 'Adding the value
End If
Next r
SumIfConditionsMetArray = tot 'Returning the calculated sum
Exit Function
''' END '''
errInFunction:
Err.Raise Err.Number, , "Error in function SumIfConditionsMetArray. Check the parameters are inside the bounds."
End Function
有点棘手,但更灵活。您可以使用范围为:
Dim MyArr as variant
MyArr = ActiveSheet.range("A1:G10").Value ' Note: use ".Value" at end
' and not start with "Set"
Debug.Print SumIfConditionsMetArray(4, MyArr, True, 100, null, 100)
' This will add the value of the 4th column, were the row
' has 100 in the first column and 100 in the 3rd column.
希望对您的问题有所帮助。
此致,Andres
** 注1 **当以arr(c,r)
的形式拥有数组时,您可以通过在括号内给出坐标来访问任何元素。例如,如果要访问第2行的第4列的值,则必须编写arr(4,2)
代码,并且您将获得值5(假设您正在测试相同的问题示例。请将其检入你的第一张表。)
** 注释2 **我有arr(c,r)
而不是arr(r,c)
的原因。原因是,如果要在最后一个位置使用行坐标,则使用redim
指令添加更多行会更容易。但是如果您的2D数组来自excel范围(例如使用arr = range("A3:D6").value
之类的东西),那么最好翻转代码中的r和c位置。