保证列包含在转换中
Andre 指出,我的最后一个答案未能解决显式 PIVOT 列列表的一个功能,即即使数据不包含相应的值,它也能保证列。在许多情况下,正如 David W Fenton 所评论的那样,“即时”生成完整的 SQL 可能同样好。这是一些这样做的模板代码:
Public Function GenerateTransform(valueArray As Variant) As String
Dim sIN As String
Dim i As Integer, delimit As Boolean
If (VarType(valueArray) And vbArray) = vbArray Then
For i = LBound(valueArray) To UBound(valueArray)
sIN = sIN & IIf(delimit, ",", "") & valueArray(i)
delimit = True
Next i
If Len(sIN) > 0 Then sIN = "IN (" & sIN & ")"
End If
GenerateTransform = "TRANSFORM ... SELECT ... PIVOT ... " & sIN
End Function
Public Sub TestGenerateTransform()
Dim values(0 To 2) As Integer
values(0) = 1
values(1) = 4
values(2) = 12
Debug.Print GenerateTransform(values)
Debug.Print GenerateTransform(vbEmpty) 'No column list
End Sub
像我的其他答案一样,以下查询允许人们利用各种^ 技术来选择和过滤条件。这种技术不仅可以保证列,还可以对行进行更多控制^^。
^ 尽管 VBA 函数仍然可以在 SQL 中正常使用,但 Access 不允许在 SQL 执行期间使用 VBA 动态添加新的行数据...行必须基于实际的表行。(从技术上讲,可以使用带有文字值的 UNION SELECT 来创建行,但这对于大量数据来说是禁止的,并且不利于任何类型的动态列选择。)因此,以下技术需要使用辅助表来定义/选择列值。
第一个查询应用选择标准并进行初始分组。如果您与我的其他答案进行比较,这与原始的 TRANSFORM 查询基本相同——只是没有 TRANSFORM 和 PIVOT。保存并命名此查询[1 Initial Aggregate]:
SELECT Agreement.City, Month([ServiceDate]) AS [Month], Count(Services.ID) AS Schedules
FROM Agreement INNER JOIN Services ON Agreement.Account = Services.Account
WHERE (Services.Code = "IS")
GROUP BY Agreement.City, Month([ServiceDate])
ORDER BY Agreement.City
接下来创建一个对所有所需行值进行分组的查询。在此示例中,我选择仅包含初始选择标准中的相同值。^^ 这组值也可以通过基于未过滤的表或其他查询来与先前的选择标准分离。保存并命名此查询[2 Row Headings]:
SELECT RowSource.City AS City
FROM [1 Initial Aggregate] AS RowSource
GROUP BY RowSource.City
ORDER BY RowSource.City
创建行标题和包含列标题的辅助表 [PivotValues] 的交叉连接。交叉连接从两个表的每个组合创建行——在 Access SQL 中,它是通过排除所有 JOIN 关键字来完成的。保存并命名此查询[3 Cross Join]:
SELECT [2 Row Headings].City AS City, PivotValues.Values AS Months
FROM [2 Row Headings], PivotValues
ORDER BY [2 Row Headings].City, PivotValues.Values;
最后,转换:通过使用 LEFT JOIN,这将包括交叉连接查询中存在的所有列和行。 对于连接选择查询中缺少数据的列和行对,该列仍将被包含(即保证),并以 Null 作为值。 尽管我们已经对初始查询进行了分组,但转换要求我们无论如何都要重新分组——也许有点多余,但对于获得对最终交叉表结果的所需控制来说没什么大不了的。
TRANSFORM Sum([1 Initial Aggregate].Schedules) AS SumOfSchedules
SELECT [3 Cross Join].City AS City
FROM [3 Cross Join] LEFT JOIN [1 Initial Aggregate] ON ([3 Cross Join].Months = [1 Initial Aggregate].Month) AND ([3 Cross Join].City = [1 Initial Aggregate].City)
GROUP BY [3 Cross Join].City
PIVOT [3 Cross Join].Months
仅仅为了使交叉表列动态化,这似乎有点矫枉过正,但值得定义一些额外的查询以完全控制结果。VBA代码可以用来(重新)定义辅助表中的值,从而满足原来使用VBA动态指定列的问题。