Access VBA:在两列上使用 GROUP BY 计算数据的中值
Access VBA: Calculating Median on data using GROUP BY on two columns
我正在尝试找到一种方法来计算访问中数据集的中位数,该数据集按两列分组,typeA, typeB
。
这是 table 的示例:
ID(自动编号)
typeA(大号)
B型(大号)
总计(大数)
1
1
1
15
2
2
1
15
3
1
1
45
4
2
1
44
5
1
2
19
6
1
2
4
7
1
2
34
8
2
2
19
9
2
2
18
使用 Access 2016
目前我正在使用以下代码片段:
Function fMedian(SQLOrTable, GroupFieldName, GroupFieldValue, GroupFieldName2, GroupFieldValue2, MedianFieldName)
DoCmd.SetWarnings False
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs1 = db.OpenRecordset(SQLOrTable, dbOpenDynaset)
If IsDate(GroupFieldValue) Then
GroupFieldValue = "#" & GroupFieldValue & "#"
ElseIf Not IsNumeric(GroupFieldValue) Then
GroupFieldValue = "'" & Replace(GroupFieldValue, "'", "''") & "'"
End If
If IsDate(GroupFieldValue2) Then
GroupFieldValue2 = "#" & GroupFieldValue2 & "#"
ElseIf Not IsNumeric(GroupFieldValue) Then
GroupFieldValue2 = "'" & Replace(GroupFieldValue2, "'", "''") & "'"
End If
rs1.Filter = GroupFieldName & "=" & GroupFieldValue
rs1.Sort = MedianFieldName
Set rs = rs1.OpenRecordset()
rs.Move (rs.RecordCount / 2)
If rs.RecordCount Mod 2 = 0 Then
varMedian1 = rs.Fields(MedianFieldName)
rs.MoveNext
fMedian = varMedian1 + rs.Fields(MedianFieldName) / 2
Else
fMedian = rs.Fields(MedianFieldName)
End If
End Function
就目前而言,这非常适合按一列分组,但我不知道如何让它按 typeA
和 typeB
分组。我已经通过编辑 rs1.filter
行但无济于事。
任何有关代码的帮助或更好的方法将不胜感激。
谢谢!
注意:使用下面的冻糕解决方案解决了。在函数结束前添加行 medianVBA = fmedian
。
请原谅我在这里采取完全不同的方法...
假设您有一个名为 Table1
的 Table,字段为 Field1
。
要找到 Field1
的中位数,SQL 查询将如下所示:
SELECT TOP 1
((SELECT MAX(B.Field1) AS Field1 FROM
(SELECT TOP 50 PERCENT A.Field1 FROM Table1 A)
B) +
(SELECT MIN(D.Field1) AS Field1 FROM
(SELECT TOP 50 PERCENT C.Field1 FROM Table1 C ORDER BY C.Field1 DESC)
D))
/2 AS MEDIAN FROM Table1
(上面拆分出来方便阅读,我只写了两行)
从那里你所要做的就是编写 vba 使其动态 - 将 'Table1' 替换为你的变量 SQLorTable
并将 Field1
替换为要查找的字段
的中位数
考虑扩展@Fionnuala 的伟大 answer,通过容纳不限成员名额的分组变量来计算 MS Access 中的中位数。
VBA (下面保存在Access项目的标准模块中)
代码为 DAO 记录集调用构建一个动态 SQL 字符串,以便稍后进行中值计算。具有 0-2 条记录的分组和分组的空值需要特殊处理。
Public Function MedianVBA(ParamArray Arr() As Variant) As Double
On Error GoTo ErrHandle
Dim N As Long
Dim tblName As String, numCol As String, grpVals As String
Dim strSQL As String
Dim db As DAO.Database, rs As DAO.Recordset
Dim varMedian As Double, fMedian As Double
'BUILD DYNAMIC SQL
tblName = Arr(0)
numCol = Arr(1)
grpVals = " WHERE " & numCol & " IS NOT NULL "
For N = 2 To UBound(Arr) Step 2
If Arr(N + 1) = "" Or IsNull(Arr(N + 1)) Then
grpVals = grpVals & " AND " & Arr(N) & " IS NULL"
ElseIf IsDate(Arr(N + 1)) Then
grpVals = grpVals & " AND " & Arr(N) & " = #" & Arr(N + 1) & "#"
Else
grpVals = grpVals & " AND CStr(" & Arr(N) & ") = '" & Arr(N + 1) & "'"
End If
Next N
strSQL = "SELECT " & numCol _
& " FROM " & tblName _
& grpVals _
& " ORDER BY " & numCol
'CALCULATE MEDIAN
Set db = CurrentDb
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
If rs.RecordCount = 0 Then
MedianAcc = fMedian
GoTo ExitHandle
ElseIf rs.RecordCount = 1 Then
MedianAcc = rs.Fields(numCol)
GoTo ExitHandle
End If
rs.Move (rs.RecordCount / 2)
rs.MovePrevious
If rs.RecordCount Mod 2 = 0 Then
varMedian = rs.Fields(numCol)
If rs.RecordCount = 2 Then
rs.MoveLast
Else
rs.MoveNext
End If
fMedian = (varMedian + rs.Fields(numCol)) / 2
Else
fMedian = rs.Fields(numCol)
End If
rs.Close
MedianAcc = fMedian
ExitHandle:
Set rs = Nothing: Set db = Nothing
Exit Function
ErrHandle:
MsgBox Err.Number & ": " & Err.Description, vbCritical, "RUNTIME ERROR"
Resume ExitHandle
End Function
请注意,上面的 VBA 函数使用 ParamArray
,其中第一个参数需要源 table,第二列需要数字列,其余的对于组列是开放式的名称和值对。通话签名如下:
=MedianAcc("table_name",
"numeric_col",
"group1_column", "group1_value",
"group2_column", "group2_value",
...)
SQL (调用上面VBA函数的存储查询)
下面运行一组和两组中值计算。
SELECT t.typeA, t.typeB
, MedianVBA('[myTable]', '[total]', '[typeA]', t.typeA) AS MedianGrp1,
, MedianVBA('[myTable]', '[total]', '[typeA]', t.typeA, '[typeB]', t.typeB) AS MedianGrp2
FROM myTable t
GROUP BY t.typeA, t.typeB
我正在尝试找到一种方法来计算访问中数据集的中位数,该数据集按两列分组,typeA, typeB
。
这是 table 的示例:
ID(自动编号) | typeA(大号) | B型(大号) | 总计(大数) |
---|---|---|---|
1 | 1 | 1 | 15 |
2 | 2 | 1 | 15 |
3 | 1 | 1 | 45 |
4 | 2 | 1 | 44 |
5 | 1 | 2 | 19 |
6 | 1 | 2 | 4 |
7 | 1 | 2 | 34 |
8 | 2 | 2 | 19 |
9 | 2 | 2 | 18 |
使用 Access 2016
目前我正在使用以下代码片段:
Function fMedian(SQLOrTable, GroupFieldName, GroupFieldValue, GroupFieldName2, GroupFieldValue2, MedianFieldName)
DoCmd.SetWarnings False
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs1 = db.OpenRecordset(SQLOrTable, dbOpenDynaset)
If IsDate(GroupFieldValue) Then
GroupFieldValue = "#" & GroupFieldValue & "#"
ElseIf Not IsNumeric(GroupFieldValue) Then
GroupFieldValue = "'" & Replace(GroupFieldValue, "'", "''") & "'"
End If
If IsDate(GroupFieldValue2) Then
GroupFieldValue2 = "#" & GroupFieldValue2 & "#"
ElseIf Not IsNumeric(GroupFieldValue) Then
GroupFieldValue2 = "'" & Replace(GroupFieldValue2, "'", "''") & "'"
End If
rs1.Filter = GroupFieldName & "=" & GroupFieldValue
rs1.Sort = MedianFieldName
Set rs = rs1.OpenRecordset()
rs.Move (rs.RecordCount / 2)
If rs.RecordCount Mod 2 = 0 Then
varMedian1 = rs.Fields(MedianFieldName)
rs.MoveNext
fMedian = varMedian1 + rs.Fields(MedianFieldName) / 2
Else
fMedian = rs.Fields(MedianFieldName)
End If
End Function
就目前而言,这非常适合按一列分组,但我不知道如何让它按 typeA
和 typeB
分组。我已经通过编辑 rs1.filter
行但无济于事。
任何有关代码的帮助或更好的方法将不胜感激。
谢谢!
注意:使用下面的冻糕解决方案解决了。在函数结束前添加行 medianVBA = fmedian
。
请原谅我在这里采取完全不同的方法...
假设您有一个名为 Table1
的 Table,字段为 Field1
。
要找到 Field1
的中位数,SQL 查询将如下所示:
SELECT TOP 1
((SELECT MAX(B.Field1) AS Field1 FROM
(SELECT TOP 50 PERCENT A.Field1 FROM Table1 A)
B) +
(SELECT MIN(D.Field1) AS Field1 FROM
(SELECT TOP 50 PERCENT C.Field1 FROM Table1 C ORDER BY C.Field1 DESC)
D))
/2 AS MEDIAN FROM Table1
(上面拆分出来方便阅读,我只写了两行)
从那里你所要做的就是编写 vba 使其动态 - 将 'Table1' 替换为你的变量 SQLorTable
并将 Field1
替换为要查找的字段
考虑扩展@Fionnuala 的伟大 answer,通过容纳不限成员名额的分组变量来计算 MS Access 中的中位数。
VBA (下面保存在Access项目的标准模块中)
代码为 DAO 记录集调用构建一个动态 SQL 字符串,以便稍后进行中值计算。具有 0-2 条记录的分组和分组的空值需要特殊处理。
Public Function MedianVBA(ParamArray Arr() As Variant) As Double
On Error GoTo ErrHandle
Dim N As Long
Dim tblName As String, numCol As String, grpVals As String
Dim strSQL As String
Dim db As DAO.Database, rs As DAO.Recordset
Dim varMedian As Double, fMedian As Double
'BUILD DYNAMIC SQL
tblName = Arr(0)
numCol = Arr(1)
grpVals = " WHERE " & numCol & " IS NOT NULL "
For N = 2 To UBound(Arr) Step 2
If Arr(N + 1) = "" Or IsNull(Arr(N + 1)) Then
grpVals = grpVals & " AND " & Arr(N) & " IS NULL"
ElseIf IsDate(Arr(N + 1)) Then
grpVals = grpVals & " AND " & Arr(N) & " = #" & Arr(N + 1) & "#"
Else
grpVals = grpVals & " AND CStr(" & Arr(N) & ") = '" & Arr(N + 1) & "'"
End If
Next N
strSQL = "SELECT " & numCol _
& " FROM " & tblName _
& grpVals _
& " ORDER BY " & numCol
'CALCULATE MEDIAN
Set db = CurrentDb
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
If rs.RecordCount = 0 Then
MedianAcc = fMedian
GoTo ExitHandle
ElseIf rs.RecordCount = 1 Then
MedianAcc = rs.Fields(numCol)
GoTo ExitHandle
End If
rs.Move (rs.RecordCount / 2)
rs.MovePrevious
If rs.RecordCount Mod 2 = 0 Then
varMedian = rs.Fields(numCol)
If rs.RecordCount = 2 Then
rs.MoveLast
Else
rs.MoveNext
End If
fMedian = (varMedian + rs.Fields(numCol)) / 2
Else
fMedian = rs.Fields(numCol)
End If
rs.Close
MedianAcc = fMedian
ExitHandle:
Set rs = Nothing: Set db = Nothing
Exit Function
ErrHandle:
MsgBox Err.Number & ": " & Err.Description, vbCritical, "RUNTIME ERROR"
Resume ExitHandle
End Function
请注意,上面的 VBA 函数使用 ParamArray
,其中第一个参数需要源 table,第二列需要数字列,其余的对于组列是开放式的名称和值对。通话签名如下:
=MedianAcc("table_name",
"numeric_col",
"group1_column", "group1_value",
"group2_column", "group2_value",
...)
SQL (调用上面VBA函数的存储查询)
下面运行一组和两组中值计算。
SELECT t.typeA, t.typeB
, MedianVBA('[myTable]', '[total]', '[typeA]', t.typeA) AS MedianGrp1,
, MedianVBA('[myTable]', '[total]', '[typeA]', t.typeA, '[typeB]', t.typeB) AS MedianGrp2
FROM myTable t
GROUP BY t.typeA, t.typeB