在 VBA 中执行存储过程的两种方法,哪一种更好?

Posted

技术标签:

【中文标题】在 VBA 中执行存储过程的两种方法,哪一种更好?【英文标题】:Two ways to execute a Stored procedure in VBA, Which one is better? 【发布时间】:2014-06-20 13:40:13 【问题描述】:

背景:工作于前端 Ms-Access 2010 和后端 SQL server 2008 Managment Studio

为了执行存储过程,我一直在使用一个相当长的过程,如下所示:在 VBA 中

    Set Conn = New ADODB.connection
    Conn.ConnectionString = "Provider=SQLOLEDB.1;Integrated Security=SSPI;....."
    Conn.Open

    Set cmd = New ADODB.Command
    cmd.ActiveConnection = Conn
    cmd.CommandType = adCmdStoredProc
    cmd.CommandText = "upGetTestIdForAnalyte"

    cmd.Parameters.Append cmd.CreateParameter("@WOID", adVarChar, adParamInput, 60, MySampleName)
    cmd.Parameters.Append cmd.CreateParameter("@Analyte", adVarChar, adParamInput, 60, MyAnalyte)
    cmd.Parameters.Append cmd.CreateParameter("@SampleID", adVarChar, adParamInput, 60, MyConcentration

    cmd.Execute
    Conn.Close

有人告诉我有一种更好的方法来执行存储过程,正确的方法是这样的:在 VBA 中

strsql = "Exec upGetTestIdForAnalyte(WOID, Analyte, SampleID)"
test = ExecuteNonQuery(strsql)

但是我在这个过程中遇到了很多错误,我查看了 ExecuteNonQuery,它说它只适用于 VB (http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executenonquery.aspx)。此过程的优点之一是其简洁性以及它自动连接到当前数据库的事实。我将不得不更改当前 sp 进程中的连接字符串,因为它被设置为链接到测试数据库而不是实际数据库。另一个优点是它在执行时返回一个值。我希望我当前的流程能够做到这一点,因为我想以某种方式验证它是否运行,而我需要制作的其他存储过程需要返回受影响的记录数。

我的问题是:第二种方法是合法的过程吗?那是正确的语法吗?每个过程完成的内容也有什么区别吗?还有一种方法可以让第一个进程在执行时返回一个值吗?感谢您的宝贵时间。

更新:这是我目前正在处理的存储过程。我的 sp 查看 testID 是否存在,如果变量 ThisTestId >0,我将在调用 sp 后继续执行程序,否则我将引发错误 testid not found

CREATE PROCEDURE upGetTestIDForAnalyte @WOID nvarchar(60), @SampleID nvarchar(60),@Analyte nvarchar(60), @Test var(20) output
AS

  SELECT TestID = t1.TestID
  FROM tblWOSampleTest t1
  JOIN tblTest t2
  ON t1.TestID=t2.TestID; 
  WHERE @WOID = t1.WOID AND @SampleID = t1.SampleID AND @Analyte = t2.Analyte

GO

【问题讨论】:

ExecuteNonQuery 用于 .Net 的数据类,而不是用于 ADO。 ADO 中不使用 Parameter 对象的类似方法没有提供针对 SQL 注入的内置保护,这意味着您必须自己清理任何用户输入 - 这是它们的主要优势。如果您查看 ADODB.Command 调用的模式,您可以将连接/声明封装在单个函数中并调用它。 哦,好吧,所以基本上如果我使用 ADO 对象,第二种方法将不起作用 【参考方案1】:

这里发布的示例代码太多了。

最初的问题是发帖者看到了一些只需要一两行代码的例子。

事实上,这段代码可以工作并将参数传递给存储过程。

With CurrentDb.QueryDefs("MyPass")
  .SQL = "exec MySproc" & ProducutID
  .Execute
End If

上面只有两行代码。

这种方法的优点?

请注意我们如何不必弄乱(或传递)连接字符串。

注意我们不必在代码中声明任何连接对象。

请注意我们如何不必在代码中的任何位置存储或拥有用户名或密码。

注意我们也不必在代码中创建查询定义对象。

事实上,整个事情都不需要声明任何变量。

以上内容本可以用两行代码编写,但我决定发布大量的 4 行代码以提高可读性。

这种设置的优点很多,但主要原因是这种方法最简单且易于维护。

此处发布的其他解决方案仅用于迫使人们编写多行代码——这只会增加其雇主的开发成本。

【讨论】:

当然我的意思是输入 querydefs(如此编辑)。该参数是 VBA 长变量,因此不可能进行字符串和 SQL 注入)。在大多数情况下,该值来自强类型 VBA 变量,例如日期或数字。大多数表格都会拒绝多余的“;”当进入这样的控制。当然,一个例外是基于字符串的文本框。所以注入是一个问题,但是对于字符串,我有一个全局函数可以删除所有杂散符号,例如 () 和“;”。我的 2 行解决方案可能是唯一一个输入为空气代码的解决方案,这解释了错字。所以我更正为 querydefs("myPass") 那么“MyPass”是否代表传递给 ProductID 的值?如果是这样,将值传递给多个参数的语法是什么?还有没有办法知道有多少行受到影响? MyPass 是保存的 passthrough 查询的名称。如果您需要其他参数,则只需添加“,”和另一个值。语法与您在 sql 管理器中键入的语法相同。至于返回的行影响?我不知道 Store 程序每个返回行都会受到影响吗?但是,如果您的 sproc 的最后一行选择 @@RowCount,那么是的,受影响的记录将被返回。代替执行,您将因此使用: .OpenRecordset()("RecordsAffected") 假设存储过程的最后一行是 select @@rowcount as RecordsAffected【参考方案2】:

我将继续使用第一个进程并添加如下输出参数:

ccmd.parameters.Append ccmd.CreateParameter("OutParam", adInteger, adParamOuput, , NULL)   ' output parameter

您需要在存储过程中添加此参数,如下所示:

@OutParam int OUTPUT

EDIT 添加了 OUT 参数,在 VBA 代码中改为整数。看看@@ROWCOUNT 是如何工作的

CREATE PROCEDURE upGetTestIDForAnalyte @WOID nvarchar(60), @SampleID nvarchar(60),@Analyte nvarchar(60), @RecordsAfected int OUT
AS

SELECT TestID = t1.TestID
FROM tblWOSampleTest t1
JOIN tblTest t2
ON t1.TestID=t2.TestID; 
WHERE @WOID = t1.WOID AND @SampleID = t1.SampleID AND @Analyte = t2.Analyte
set @recordsAfected = @@ROWCOUNT
GO

【讨论】:

好的,所以一旦我将此输出参数添加到 sp 和进程中,可以将 Outparam 用作变量吗?如果我将 OutParam 设置为等于我的 sp 中的某个值,那么在执行 sp 以检索值之后,我是否可以在我的过程中设置 outparam 等于某个值? @VictoriaJay 完全正确。如果您需要此步骤的帮助,请分享您的 sp 我在我的 VBA 代码中使用了这个,所以 ThisTestID 可以存储值,这是正确的语法吗? cmd.Parameters.Append cmd.CreateParameter("@recordsAffected", adInteger, adParamOutput, 20, ThisTestID) @VictoriaJay 大小是可选的,最好这样: cmd.Parameters.Append cmd.CreateParameter("@recordsAffected", adInteger, adParamOutput, , ThisTestID)【参考方案3】:

来自Microsoft help site:

   Function ExecuteSPT (sqltext As String, connectstring As String)

   ' Purpose: Run a temporary pass-through query.
   ' Accepts: sqltext: SQL string to run.
   '          connectstring: Connection string, which must be at least
   '          "ODBC;".
   ' Returns: nothing.

   Dim mydb As Database, myq As QueryDef
   Set mydb = DBEngine.Workspaces(0).Databases(0)

   ' Create a temporary QueryDef object that is not saved.
   Set myq = mydb.CreateQueryDef("")

   ' Set the ReturnsRecords property to False in order to use the
   ' Execute method.
   myq.returnsrecords = False

   myq.connect = connectstring
   myq.sql = sqltext

   myq.Execute
   myq.Close

   End Function

它可以很容易地更改为以 DAO.Recordset 的形式返回。 DAO 仍然是 MS Access 中的“本机”数据访问。

【讨论】:

当你说代码可以被修改以返回一个 DAO.Recordset,你的意思是什么? DAO = 数据访问对象,这是 Access 的标准模型。也可以使用 ADO 等其他方法,但如果在 Form 中使用 Me.Recordset,则会得到 DAO.Recordset。

以上是关于在 VBA 中执行存储过程的两种方法,哪一种更好?的主要内容,如果未能解决你的问题,请参考以下文章

找出后缀数组的两种算法中哪一种更快,为啥?

在 VBA 中导出存储过程的参数

哪一种是真正的冒泡排序,哪一种更好?

目前最受客户欢迎的两种“上网卡”,不知道你现在用的哪一种?

Python遍历文件夹的两种方法

如何监控Oracle数据库中长时间运行的进程