如何等待 Power Query 刷新完成?

Posted

技术标签:

【中文标题】如何等待 Power Query 刷新完成?【英文标题】:How to wait for a Power Query refresh to finish? 【发布时间】:2018-06-08 14:22:49 【问题描述】:

设置:

Windows 7(工作中) Windows 10(在家) Excel 2016(工作中构建 4627) Excel 2016(在家构建 8730) Power Query 设置为导入、附加和转换 Excel 文件文件夹。此步骤有效。

使用任何技术等待 Power Query 完成刷新后,如下所示的“我尝试过的事情:”部分所述,可以显示一个消息框,并且可以在 Power Query 表之前执行任何其他代码根据查询和连接窗格中显示的刷新指示器(微调器?)图标完成更新。

上述语句的例外是 Application 类的 OnTime 方法,如下面的“代码”部分所示,它似乎不会中断电源查询刷新的轮询。问题是它使用硬编码的时间量来暂停 VBA 代码,这并不总是有效,因为被查询的数据的大小、数量和持续时间会随着时间而改变。

我试过了:

我已阅读所有声明使用 DoEventsBackgrgoundQuery = FalseCalculateUntilAsyncQueriesDone 方法和属性的 ***(和其他网站资源)。 我尝试按照此链接的建议为Create Before/After Query Update Events 创建一个类(以下代码示例中未显示)。 我尝试使用 Do Until/While 循环和 QueryTable 方法的 .Refreshing = True/False 属性来等待刷新完成。 我尝试将 Excel 菜单(菜单栏 --> 数据 --> 连接 --> 属性)中的 BackgroundQuery 属性设置为 False,如此处“subro”所建议的:Wait until ActiveWorkbook.RefreshAll finishes - VBA,此处带有菜单图像:

.

代码:

Private Sub sht_sub_Refresh_AllConnections_dev()
    'Name: sht_sub_Refresh_AllConnections_dev
    'Purpose: An attempt at using VBA to wait for Queries to finish updating before displaying a message.
    'Description: Waits for a hard coded period of time before dislpaying the message box.
    'State: WIP.
    'Dev: Needs a way to look at the connection stream to somehow detect when its finished.
    
    'DECLARATIONS:
    '------------'
    Dim procName As String              'Stores this procedure's name.
    Dim qTblLst As QueryTables          'A query table collection object.
    Dim qTblObj As QueryTable           'A query table object.
    Dim conLst As Connections           'A connection collection object.
    Dim conObj As WorkbookConnection    'A connection object.
    Dim idx As Long                     'A loop counter.

    'INITIALIZATIONS:
    '---------------'
    procName = "sht_sub_Refresh_AllConnections_dev"    'Store this procedure's name.
    Linit.ini_Setup_Project                            'Setup the project if needed.
    Set conLst = ThisWorkbook.Connections              'Set the connections list object.
    Set conObj = conLst.Item(conLst.Count)             'Set an initial connection object.
    idx = 0                                            'As an exit if the do loop continues without end.
    
    'MAIN CODE BODY:
    '--------------'
    'Turn off backgroundquery for each connection type.
    For Each conObj In conLst                           'For each connection object,
        With conObj
            Select Case .Type                               'Check the connection type,
            Case 1                                        'If its an OLEDB connection then,
                .OLEDBConnection.BackgroundQuery = False    'Set it's backgroundquery property to false.
            Case 2                                        'If its an ODBC connection the,
                .ODBCConnection.BackgroundQuery = False     'Set it's backgroundquery property to false.
            End Select
        End With
    Next conObj
    
    ThisWorkbook.RefreshAll                             'Refresh all connections.
    
    'DEV: Using loops, DoEvents and a query name starting with the letters "zzzz" as suggsted here:
    'https://social.technet.microsoft.com/Forums/en-US/bc3f7748-8a52-498d-951c-4566b8adf45a/in-excel-2016-power-queries-dont-refresh-in-the-background-anymore?forum=powerquery
    'and here:
    'https://www.myonlinetraininghub.com/excel-forum/vba-macros/pause-macro-until-power-queries-finished-refreshing
    'Attempt to wait until the last connection has finished refreshing.
    Do Until Linit.gvTbl_ZZZZZ.QueryTable.Refreshing = True   'Wait until the last table starts refreshing,
        idx = idx + 1                                           'Icrement a loop count,
        If idx > 3000 Then Exit Do                              'If the loop goes longer then 3000 iterations exit,
    Loop                                                      'otherwise continue waiting.
    VBA.DoEvents                                              'Do events before continueing (doens't work).
    Do Until Linit.gvTbl_ZZZZZ.QueryTable.Refreshing = False  'Wait until the last table finishes refreshing,
        idx = idx + 1                                           'Icrement a loop count,
        If idx > 3000 Then Exit Do                              'If the loop goes longer then 3000 iterations exit,
    Loop                                                      'otherwise continue waiting.
    VBA.DoEvents                                              'Do events before continueing (doens't work).
    'DEV: The following is an attempt to get connections to
    '     finish refreshing before code continues as suggested here:
    'https://***.com/questions/22083668/wait-until-activeworkbook-refreshall-finishes-vba
    Application.CalculateUntilAsyncQueriesDone         'This is placed here as well as after the refresh.
    VBA.DoEvents                                              'Do events before continueing (doens't work).
    Application.EnableEvents = False                          'Maybe turning off events helps? (nope...),
    Application.ScreenUpdating = False 'This is reset in the procedure called as an argument to the next line:
    Application.OnTime DateAdd("s", 3, Now), _
                       "Lwksh.sht_sub_Msg_RefreshDone"        'The called procedure just displays a message box.
    Application.EnableEvents = True                           'Restore events,
    Application.ScreenUpdating = True                         'Restore screen updating.
    
    'MEMORY CLEANUP:
    '--------------'
EXIT_CLEAN:
    procName = Empty                                     
    Set qTblLst = Nothing
    Set qTblObj = Nothing
    Set conLst = Nothing
    Set conObj = Nothing
    idx = 0
End Sub

代码注释:

代码中以“Linit”开头的任何内容。是一个对象或变量,由代码的“INITIALIZATIONS:”部分中的“Linit.ini_Setup_Project”过程调用在过程之外全局设置。 例如,“Linit.gvTbl_ZZZZZ”是一个对象变量,它指向一个空的单行表,该表的名称带有“zzzz”字符,由 Power Query 生成并加载到 Excel 工作表。该代码显示了指向提出使用此类空表的建议的网站的链接。

问题:

    这是因为 Power Query 没有内置回调让 Excel 知道它已完成更新任何刷新过程而导致失败吗? 如果这不是丢失的原因,是否有任何其他方法(此处未描述)可用于在连接尚未完成刷新时以某种方式触发错误,或者在连接未完成刷新时触发错误连接完成了吗? (这里的想法是,如果错误不会阻止查询完成,则此错误可能会被捕获为检测天气或刷新未完成的一种可能方式。 有什么方法可以直接使用 VBA 探测连接流以查找连接关闭或完成状态? 有没有什么方法可以通过调用 Excel 之外的程序直接访问刷新过程? 您能想出其他可以尝试或测试的方法来完成这项工作吗?我会继续自己寻找答案,但经过一整年的搜索,我感觉有点不走运。

【问题讨论】:

@Mike:“TLDR”被理解了。包含所有信息是为了完整地描述问题,这为那些愿意略读以从问题中获得所需内容的人提供了清晰的信息。谢谢你的回答。在确保您发布的内容有效或仍然无效后,我会在您的帖子下进一步评论。 它很长,但恕我直言,问题清楚地列出了,您尝试、研究和编码的内容,考虑到您的新功能,做得很好! 【参考方案1】:

我理解你的痛苦@neurojelly。我去过那里。但事实证明,解决方案非常简单,并且不使用 VBA。 在查询属性窗口中,您需要取消选中“启用后台刷新”,然后使用DoEvents。 我确信这很有效,因为我已经使用这种方法一年多了。

请找到包含代码的示例文件的链接。 https://drive.google.com/uc?export=download&id=1ZLxSMEXPLda3QhaQoTyGGv3_sC-tpN-X

至于第二个问题,可以使用Iferror/OnEror 方法来检测查询是否返回错误,但不一定检查查询中的错误。它识别查询本身是否返回错误弹出窗口,在运行 VBA 代码时默认跳过该错误弹出窗口。这种方法大部分时间都有效,但并非总是如此。

【讨论】:

【参考方案2】:

这里有一个解决方法

Sub MyProcedure()

    '
    ' Some procedures
    '
    Call ActiveWorkbook.RefreshAll
    Call NotifyWhenRefreshComplete
End Sub



Private Sub NotifyWhenRefreshComplete()
    Const PulseTimer As Currency = TimeValue("00:00:01")
    Dim b1 As Boolean, b2 As Boolean

    b1 = Sheet1.Range("ListObject1").ListObject.QueryTable.Refreshing
    b2 = Sheet1.Range("ListObject2").ListObject.QueryTable.Refreshing

    If b1 Or b2 Then
        Call Application.OnTime(Now + PulseTimer, "NotifyWhenRefreshComplete")
    Else
        Call MsgBox("Refresh Complete.", vbOKOnly)
    End If
End Sub

ListObject1 和 ListObject2 是已发布的表。仅需要已发布的表来检查刷新是否完成。您不必检查未发布的表格。

但是,如果您有很多已发布的表,那么遍历所有ActiveWorkbook.connections 并检查每个wbConn.OLEDBConnection.Refreshing 状态是否已返回false 状态,替换b1 和@ 并没有错987654326@布尔值。

注意:由于某些原因,我拒绝使用DoEvents,我希望我的用户能够在连接仍在运行时继续使用 Excel,并在刷新完成时提示他们一条消息。但是,如果您想使用 Do... Loop 迭代而不是上面所示的 OnTime 调用者来实现它,请继续。

最后Public WithEvents qt As QueryTable下有个回调,找qt.refreshing。您也可以使用此方法。

希望这会有所帮助。

【讨论】:

【参考方案3】:

我在 ThisWorkbook.Connections.Ranges(1).ListObject.QueryTable 上调用 .Refresh 方法,BackgroundQuery:=False。这似乎很可靠 - 到目前为止,我的宏可能已经以非常低的错误/冻结率调用了该方法超过 10,000 次。

据我了解,当 PQ 在 Excel 2016 的对象模型中正确公开时,所有这些 DoEvents 变通办法等都已过时。

这是一个粗略的代码示例:

Dim cn As WorkbookConnection
For Each cn In ThisWorkbook.Connections
    cn.Ranges(1).ListObject.QueryTable.Refresh BackgroundQuery:=False
Next cn

在 For 循环中,您可以检查 cn.Name 来控制单个查询的执行。 Name 属性遵循工作簿连接名称,例如“查询 -” & PQ 查询名称。

【讨论】:

由于以下原因,很难测试这是否有效。我的代码中由查询生成的主表依赖于仅连接查询,并且您提供的代码挂起运行时错误'-2147418113(8000ffff)“对象'Ranges'的方法'_Default'失败,在For Each 循环中心的行。错误仅发生在我的测试中的仅连接查询上。我的仅连接查询是一个函数,有助于为后续查询相对于工作簿位置创建源。因此不能输出到表格。有什么建议吗? 好的,我已经使用更静态的方法进行了测试,只需设置一个表示表的连接对象并使用与 for 循环示例中心相同的字符串。在我的情况下,如下所示:conObj.Ranges(1).ListObject.QueryTable.Refresh BackgroundQuery:=False 但是代码执行仍在继续,而刷新仍在发生。换句话说,当消息框显示并关闭时,查询和连接窗格中显示的微调器仍在旋转。在刷新过程中也可以选择工作簿中的其他单元格。 如果有帮助并且允许我这样做,我会将构建版本添加到我的原始帖子中。您如何测试您的查询是否仍然在您的示例中令人耳目一新?您是否碰巧知道“查询和连接”窗格上显示的微调器是否足够准确,可以作为刷新是否成功完成的指示器被信任?由于您的示例不起作用,您还有其他想法吗? 我只是接受我无法控制 Excel UI,所以如果微调器想要旋转,他们会的。出于此目的,该代码对我来说确实可以正常工作-它按顺序执行查询。我前后展示了一个 MsgBox 来引导用户。 我无法确定您的解决方案是否有效。似乎没有一种可验证的方法来测试查询是否确实完成了刷新。如果 VBA 可以在查询完成之前执行,那么使用 VBA 编辑查询生成的表是否是一个合适的测试?我不确定这不会导致误报。例如,如果查询仅更新自上次刷新以来需要更改的单元格,则可能出现误报。你怎么看?你将如何测试这个?换个整张桌子怎么样,这样测试就够了吗?

以上是关于如何等待 Power Query 刷新完成?的主要内容,如果未能解决你的问题,请参考以下文章

Power Query中避免刷新出错的注意事项

Excel 2016 Power Query无法加载到电子表格

Power Query加载链接并刷新的疑惑

刷新 CSV 导入时 Power Query 无法添加列

Power Query Excel 中的覆盖/锁定值

如何通过power+query将10列数据各乘1000?