如何在 VBA 中可靠刷新 Power Query 并避免 ID 数据混杂?
结论
要让报表读取当前 ID 的数据,应等该 ID 刷新完成后再复制报表。对于已加载到工作表表格的 Power Query 结果,可以调用对应 QueryTable 的 Refresh BackgroundQuery:=False,让 VBA 等待刷新完成后再处理下一个 ID。
RefreshAll 会刷新工作簿中的多个连接和查询。将连接的 BackgroundQuery 设为 False,或调用 Application.CalculateUntilAsyncQueriesDone,都不一定能保证报表循环中的查询按预期逐个完成。具体行为取决于查询的加载方式、连接和依赖关系。
按 ID 顺序刷新
下面的代码示意了执行顺序。请将 ID 的设置方式、输出工作表和表名替换为实际名称:
Sub BuildReports()
Dim ws As Worksheet
Dim lo As ListObject
Dim id As Variant
Set ws = ThisWorkbook.Worksheets("Projects")
Set lo = ws.ListObjects("Projects")
For Each id In Array("Main ID 示例", "Sub ID 示例")
' 在这里把当前 ID 写入 Power Query 使用的参数或输入单元格。
' 例如:ThisWorkbook.Worksheets("Parameters").Range("A1").Value = id
' 等待 Projects 对应的查询表刷新完成。
lo.QueryTable.Refresh BackgroundQuery:=False
' 如果报表还依赖工作表公式,在读取或复制前重新计算。
Application.Calculate
' 在这里读取当前 ID 对应的数据并生成或复制报表。
Next id
End Sub
如果查询从参数表或输入单元格读取 ID,应先写入当前 ID,再刷新输出表。查询刷新完成后,再复制数据或切换到下一个 ID。如果报表依赖其他 Power Query 输出,也应先按依赖关系刷新上游查询,再刷新报表所用的表。
检查查询是否刷新成功
QueryTable.Refresh 会返回布尔值,可以据此检查刷新是否成功:
Dim refreshed As Boolean
refreshed = lo.QueryTable.Refresh(BackgroundQuery:=False)
If Not refreshed Then
Err.Raise vbObjectError + 1000, "BuildReports", _
"Projects 查询未能完成刷新。"
End If
对于加载到工作表表格的查询,优先直接刷新对应的 QueryTable。如果只对整个工作簿调用 RefreshAll,就更难确定某个 ID 对应的数据何时可以复制。
关于已尝试的方法
你给出的代码先设置了 OLEDB 连接的 BackgroundQuery,再调用 wbMain.RefreshAll 和 Application.CalculateUntilAsyncQueriesDone;另一个片段则直接刷新 Projects 表。前一种做法涉及多个连接,仅凭调用顺序无法确认每张报表都已取得当前 ID 的数据。后一种做法适用于查询确实加载到 Projects 表的情况,但仍要先更新 ID,并在刷新完成后再复制数据。
用 Timer 固定等待 20 秒无法确认查询是否已经完成。查询可能更快,也可能更慢。应以同步刷新或实际的刷新状态为准,不要假设固定等待时间足够。
注意事项
- 如果
Projects不是查询结果实际加载到的表,刷新它不会更新所需的输出。请确认ListObjects("Projects")对应的是目标查询表。 - 如果某个 ID 的报表仍读到上一个 ID 的数据,请检查 ID 参数的更新时间、查询引用的单元格或参数,以及是否有其他宏步骤在刷新前读取或复制结果。
- 如果查询输出到数据模型、外部连接或其他加载目标,刷新方式可能与工作表
QueryTable不同,需根据实际加载目标逐项确认。 - 如果同步刷新后生成的报表仍不完整,请检查查询本身的筛选条件,以及 Main ID、Sub ID 的来源。现有信息不足以判断缺少的 Sub ID 是由刷新时序还是查询逻辑导致的。
备注:内容仅供参考。
