从VBA执行SQL Server存储过程并检索所有消息和结果集

时间:2018-07-13 14:26:16

标签: sql-server access-vba ado

我希望能够从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条)都进入了连接的Errors集合(!),但是当我将消息数量减少到大约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 将有所帮助。

1 个答案:

答案 0 :(得分:2)

要毫不客气地piggy带@Erik,您想创建一个新类来处理您的处理。类似于cProcedureHandler。在此类中,您需要使用ADODB.Connection关键字声明一个WithEvents对象:

Dim WithEvents cn As ADODB.Connection

然后,您需要编写一个InfoMessage事件处理程序,该事件处理程序将处理多个print语句。可以在here中找到有关InfoMessage事件的信息,并且可以在here中找到使用连接的Errors集合。因此,您最终将得到如下结果:

  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设置为打开。