【问题标题】:Force vba to wait for sql query to execute before continuing强制 vba 在继续之前等待 sql 查询执行
【发布时间】:2012-07-12 14:04:41
【问题描述】:

这似乎是一个相当普遍的问题,但我找到的解决方案似乎都不起作用。

我正在从 SQL Server 获取一些数据并将其复制到工作表中。然后我想从新数据中复制一个范围并用它做其他事情。所有这些都发生在一个 vba 函数中。

我的问题是当函数从 Excel 运行时,它会移动到函数的第二部分而不等待查询返回所需的数据。

当然,当我从 vba IDE 运行该功能时,它可以正常工作。

Dim a As New ADODB.Connection
Dim r As New ADODB.Recordset

a.Open (connStr)
Set r = a.Execute(sqlstr)

sht.Range("A2").CopyFromRecordset r

'please wait here until the proc has executed?

checkData = sht.Range("A2").Value

当我从 Excel 运行该函数时,checkData 始终为空,当我使用 F5 运行它时,它始终具有所需的数据。

【问题讨论】:

  • 您是否尝试在工作表函数中执行此操作?它不起作用,因为 UDF 只能返回一个值 - 它不能直接更新工作表。
  • @TimWilliams,是的,我在 vba 函数中执行此操作,其中 sqlStr 是返回所需数据的 sql 存储过程。
  • 然后只使用checkData = r.Fields(0).Value,除非您需要来自记录集中的更多数据。如果您需要其他数据,那么您将不得不找到某种方式来运行代码,而不是通过 UDF。

标签: excel vba ado


【解决方案1】:

尝试使用:

Application.CalculateUntilAsyncQueriesDone

在执行 SQL 之后,但在复制 RecordSet 之前

Set r = a.Execute(sqlstr)
    Application.CalculateUntilAsyncQueriesDone
sht.Range("A2").CopyFromRecordset r 

【讨论】:

  • 其实这应该被标记为正确答案。我在刷新网络查询时遇到了同样的问题,即在从网站下载数据之前执行了后续计算。添加语句 Applicaton.CalculateUntilAsnycQueriesDone 后,它正在等待网络查询刷新完成。
【解决方案2】:

这行得通吗?

Dim a As New ADODB.Connection
Dim r As New ADODB.Recordset

a.Open (connStr)
Set r = a.Execute(sqlstr)

Do
  'Wait
Loop Until Not r Is Nothing

sht.Range("A2").CopyFromRecordset r

checkData = sht.Range("A2").Value

或者,如果失败,您可以尝试测试 r 的某些属性,例如 EOF 或 BOF,如果发生错误,或者您得到一个意外值,您知道数据尚未加载。例如:

Dim a As New ADODB.Connection
Dim r As New ADODB.Recordset

a.Open (connStr)
Set r = a.Execute(sqlstr)

On Error Resume Next
Do
  Err.Clear
  r.EOF 'Put your test here, you might test rowcount or similar.
        'I've simply asked for the EOF property and ignored the result, I'm 
        'not sure if this will work in your case. Some testing may be required.
While Err.Num <> 0
On Error GoTo 0 'Or whatever you previously had this set to

sht.Range("A2").CopyFromRecordset r

checkData = sht.Range("A2").Value

【讨论】:

  • 感谢@mkingston 的尝试。不幸的是,循环似乎没有任何区别。对于第二种解决方案,我已经在执行 CopyFromRecordSet 作为我的标准 ado sn-p 的一部分之前检查是否返回了数据,看起来该函数正在后台运行它并继续检查数据。
  • @Skrealin 我认为这可能有点太明显了。我建议在您的 copyfromrecordset 函数中放置一些 debug.print 行,以检查它是否真正到达了代码的每个部分。如果是,则输出变量值以检查所有内容的读取/处理是否正确。
  • 我假设代码以某种方式运行并发现它不是的次数。我认为当您在其他人的环境中使用脚本语言时尤其如此。底层对象通常会做你意想不到的事情,如果你对它们没有很好的理解,很容易错过。
  • 经过大量调试后,在我尝试添加这些新东西之前,它原来是在另一个已经愉快地工作了几个月的代码中。感谢您的帮助,至少我现在对记录集有了更多的了解!
  • 好吧,我认为我与它并没有太大的关系,但我很高兴你已经解决了它! :)
【解决方案3】:

这可能会有所帮助。不要在代码中设置数据源,而是在目标工作表上将其设置为数据连接(Excel 菜单数据 | 从其他来源 | 等)。创建名为“(默认)”的连接对象后,您可以在代码中按照以下几行点击它:

  With ActiveWorkbook
    .Connections("(Default)").OLEDBConnection.BackgroundQuery = False
    .Connections("(Default)").OLEDBConnection.CommandText = sqlstr
    .RefreshAll
    ' do more stuff
    ' will wait for .RefreshAll to complete because .BackgroundQuery = false
  End With

【讨论】:

    【解决方案4】:

    我认为您需要在执行后使用r.movelast 以确保返回所有行。

    类似

    Set r = a.Execute(sqlstr)
    
    If Not r.EOF Then
        r.MoveLast
    End If
    
    sht.Range("A2").CopyFromRecordset r
    

    【讨论】:

      【解决方案5】:

      按照 andy holaday 的思路,我通过取消选中外部数据范围属性中的“启用后台刷新”来实现此功能。禁用此功能会强制 excel 在查询运行时等待。

      【讨论】:

        【解决方案6】:

        但是,如果没有返回记录集,我发现这段代码将等到 SQL 代码返回,然后再转到下一个 VBA 语句。当一个命令依赖于另一个命令或者如果您需要在继续之前创建整个数据集时会很方便。

        Dim Con As ADODB.Connection
        Dim CmdTxt As String
        
        Set Con = New Connection
        Con.ConnectionString = ThisWorkbook.GetYourConnectString()
        Con.Open
        
        CmdTxt = "EXEC db.schema.StoredProc1 @Param1 = 'Yes'"
        ExecuteSql Con, CmdTxt, True, True
        
        CmdTxt = "EXEC db.schema.StoredProc2 @Param1 = 'No'"
        ExecuteSql Con, CmdTxt, True, True
        
        MsgBox "Both commands completed sequentially"
        

        ExecuteSql 的代码是:

        Public Function ExecuteSql(Con As ADODB.Connection, sql As String, _
            Optional StopOnError As Boolean = True, _
            Optional WaitUntilDone As Boolean = False) As String
        
        Dim cmd As ADODB.Command
        Set cmd = New ADODB.Command
        
        With cmd
        
            .CommandType = 1
            .CommandText = sql
            .ActiveConnection = Con
        
            If WaitUntilDone = True Then
                .CommandTimeout = 0 'set timeout to unlimited
                .Execute , , adExecuteNoRecords 'no records value speeds up internal code
            Else
                .Execute
            End If
        
        End With
        
        ExecuteSql = ""
        
        Exit Function
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-08-31
          • 2020-01-15
          • 2012-03-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-20
          相关资源
          最近更新 更多