【问题标题】:Execute SQL Server stored procedure from VBA and retrieve all messages and result sets从 VBA 执行 SQL Server 存储过程并检索所有消息和结果集
【发布时间】:2018-12-21 23:15:22
【问题描述】:

我希望能够从 MS Access VBA 执行 SQL Server 存储过程,这样我可以读取 (1) 所有结果集,而不仅仅是第一个; (2) PRINT 语句或类似语句产生的任何消息。

我有一个带有一个输入参数的测试存储过程,它产生 3 个不同的结果集和大约 90 条消息。它调用了几个子存储过程,我可以从 SSMS 中很好地执行它,但是(对我来说)不清楚如何最好地从 Access VBA 中执行它。到目前为止,我已经尝试了以下方法:

  1. DAO。使用 SQL 传递查询,我可以在 DAO 中得到很多我想要的东西,尽管它有点笨拙。它将 3 个结果集中的第一个作为记录集返回,通过使用 LogMessages 属性,我可以获得包含发出消息的表(“Admin - NN”)。

  2. ADO。使用 Connection 和 Command 对象,我可以从存储过程中获得表示第一个结果集的单个记录集。但是,我似乎无法说服它生成仅向前的记录集。关于消息,在某一时刻,所有消息(至少,我预期的大约 150 条中的前 127 条)都进入了连接的错误集合(!),但是当我将数量减少到大约 90 条时,它们都没有出现在我能找到的任何地方。

正如我一开始所说,我真正想要的是所有结果集的输出,加上消息。这可能吗?

这是我目前用于执行存储过程的例程列表:

Function ExecuteStoredProcedureADO(SPName As String, Connect As String, ReturnsRecords As Boolean, _
   ParamArray Params() As Variant) As ADODB.Recordset
   ' v1.0 2018/06/26
   ' execute stored procedure SPName on a SQL Server database specified by the string in Connect

   Dim strErr As String
   Dim i As Integer
   Dim lngRecsAffected As Long

   Dim cnn As ADODB.Connection
   Dim cmd As ADODB.Command
   Dim errCurr As ADODB.Error
   Dim rst As ADODB.Recordset

   On Error GoTo Catch
   Set ExecuteStoredProcedureADO = Nothing

   Set cnn = New ADODB.Connection
   cnn.Errors.Clear
   cnn.mode = adModeRead
   cnn.CommandTimeout = 300
   cnn.Open Connect

   Set cmd = New ADODB.Command
   With cmd
      .ActiveConnection = cnn
      .CommandText = SPName
      .CommandType = adCmdStoredProc

      For i = 0 To UBound(Params) Step 4
         .Parameters.Append .CreateParameter(Params(i), Params(i + 1), adParamInput, Params(i + 2), Params(i + 3))
      Next i
      Set rst = New ADODB.Recordset
      rst.CursorType = adOpenStatic
      If ReturnsRecords Then
         '''Set rst = .Execute(lngRecsAffected)
         rst.Open cmd, , adOpenStatic, adLockReadOnly
      Else
         Set rst = .Execute(, , adExecuteNoRecords)
      End If
   End With
   If ReturnsRecords Then Set ExecuteStoredProcedureADO = rst

Final:
   On Error Resume Next
   If Len(strErr) > 0 Then Call AppendMsg(strErr)
   Set rst = Nothing
   Set cmd = Nothing
   Exit Function

Catch:
   If cnn.Errors.Count > 0 Then
      With cnn
         For Each errCurr In cnn.Errors
            strErr = strErr & "Error " & errCurr.Number & ": " & errCurr.Description _
               & " (" & errCurr.Source & ")" & vbCrLf
         Next errCurr
         strErr = Left(strErr, Len(strErr) - 2) ' truncate final CRLF
      End With
   Else
      strErr = "Error " & Err.Number & ": " & Err.Description & " (" & Err.Source & ")"
   End If
   MsgBox strErr, vbOKOnly, gtitle
   Resume Final

End Function

附录:关于多个结果集,我希望http://msdn.microsoft.com/en-us/library/ms677569%28VS.85%29.aspx 会有一些帮助。

【问题讨论】:

  • NextRecordSet 应该可以正常工作。您只需遍历它们。我从来没有用 SQL Server 过程返回来完成它,但是我已经用从 Teradata 过程返回的 15 个结果集来完成它,它工作得很好。但不确定“消息”。
  • 要检索消息,您需要在类中使用 ADO,在类范围内使用WithEvents 声明连接,并使用 Connection_InfoMessage 事件。我会写一个完整的答案,但我目前正在手机上

标签: sql-server ms-access vba ado


【解决方案1】:

为了无耻地背负@Erik,您想创建一个新类来处理您的处理。像cProcedureHandler 这样的东西。在这个类中,您需要使用 WithEvents 关键字声明一个 ADODB.Connection 对象:

Dim WithEvents cn As ADODB.Connection

然后,您需要编写一个InfoMessage 事件处理程序来处理多个打印语句。有关InfoMessage 事件的信息可以在here 中找到,使用连接的Errors 集合可以在here 中找到。所以你最终会得到这样的结果:

  Private Sub cn_InfoMessage(ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection)
     Dim err As ADODB.Error

     Debug.Print cn.Errors.Count & " errors"

     For Each err In cn.Errors
        ' handle each error/message the way you need to.
        Debug.Print err.Description
     Next err
  End Sub

由于您已经处理了处理多条消息的代码,现在您只需要处理多个记录集,您提供的链接中对此进行了很好的解释。我注意到的一件事是示例链接使用rs is nothing 作为没有更多记录集的检查,这对我不起作用。我不得不使用 rs State 属性。所以我最终得到了这个:

  Public Sub testProcedure()
     Dim cmd As ADODB.Command
     Dim rs As ADODB.Recordset
     Dim recordSetIndex As Integer

     Set cn = modData.getConnection

     Set cmd = New ADODB.Command
     cmd.ActiveConnection = cn
     cmd.CommandType = adCmdStoredProc
     cmd.CommandText = "dbo.sp_foo"

     Set rs = cmd.Execute

     recordSetIndex = 1

     Do Until rs.State = ObjectStateEnum.adStateClosed
        Debug.Print "contents of rs #" & recordIndex
        Do Until rs.EOF
           Debug.Print rs.Fields(0) & rs.Fields(1)
           rs.MoveNext
        Loop

        Set rs = rs.NextRecordset
        recordSetIndex = recordIndex + 1
     Loop

     cn.Close
     Set rs = Nothing
     Set cn = Nothing
     Set cmd = Nothing

  End Sub

然后,当您准备好从 VBA 运行您的 SP 时,只需执行以下操作:

set obj = new cProcedureHandler
obj.testFooProcedure

另一件事(您可能已经这样做了):确保您在 SQL Server 中的实际存储过程设置了 nocount。

【讨论】:

  • 优秀的答案。如果我不写一篇,你可以随时使用我在 cmets 中提供的信息。
  • @Zack 事件是只引发一次,还是每次发出消息时引发?我期待后者,因为事件处理程序只有一个 Error 对象而不是 Errors 集合,但是当我尝试它时该事件仅引发一次,并且消息是:265929:没有更多结果。 (提供者)状态:1 我期待 90 多条消息。
  • 您不想使用事件中传递的pError 对象(正如您所注意到的,这只是一个错误),而是使用连接的Errors 集合,其中将包含您需要的所有错误/消息。
猜你喜欢
  • 2015-08-22
  • 2013-08-31
  • 1970-01-01
  • 2020-02-24
  • 2010-09-08
  • 2012-09-05
  • 1970-01-01
  • 2011-11-07
  • 1970-01-01
相关资源
最近更新 更多