在包含动态 sql 的事务中嵌入对存储过程的调用

Posted

技术标签:

【中文标题】在包含动态 sql 的事务中嵌入对存储过程的调用【英文标题】:embedding a call to a stored procedure with in a transaction that contains dynamic sql 【发布时间】:2014-01-13 15:46:51 【问题描述】:

我想在插入选择语句的末尾调用一个存储过程 但在事务的提交或回滚之前,我希望存储过程成为包含插入选择的事务的一部分。如果我希望新代码成为包含事务的一部分,那么编写动态 SQL 是唯一的选择吗?

字符串追加的数量实际上是相当大的(数千个),但我只分享了一小段代码,以突出概念并展示我想要做什么。

sqlStr.Append("BEGIN TRANSACTION; ")

sqlStr.Append("INSERT INTO table_chg (Column1, Column2) ")                                             
sqlStr.Append("SELECT r.Column8 , r.Column9 ")
sqlStr.Append("FROM v_table r, table1 r ")
sqlStr.Append("WHERE r.columnName1 = '1' ")
sqlStr.Append("  AND r.columnName2 = '2'; ")
sqlStr.Append(" ") 

/** ADD STORED PROCEDURE HERE    */

EXEC sp_storedProc '1'   **THIS DOES NOT WORK. BUT 1 does resolve to the correct variable that I need. Also the stored procedure works standalone and the VB.Net code does try to reference the stored procedure because if I give the stored procedure an incorrect name, I get an error back which says "stored procedure not found**

sqlStr.Append("IF @@ERROR <> 0 ")
sqlStr.Append("    ROLLBACK TRANSACTION ")
sqlStr.Append("ELSE ")
sqlStr.Append("    COMMIT TRANSACTION; ")

strError = DatabaseClass.ExecuteNonQueryReturnError (String.Format(sqlStr.ToString(), parameter1, parameter2))

【问题讨论】:

关于可行性的任何想法? 我认为这是您为生成示例所做的简化的副产品,但您对“v_table”和“table1”都使用了别名“r”。这将导致错误。 是的,没错。它实际上是一个巨大的 sql 语句,我减少了.....很好的观察...... 【参考方案1】:

    @@ERROR 函数在发生错误后一旦执行任何其他语句就会重置为 null,因此最好在您认为可能发生错误的地方使用变量。

    在您的连接条件中,您为两个表使用了相同的别名r,这将导致错误。别名应该不同。

    在您的联接中使用ON 子句

对于您的@@ERROR 函数语句应该类似于

@@ERROR

BEGIN TRANSACTION
 DECLARE @Error INT;

    INSERT INTO table_chg (Column1, Column2)                                             
    SELECT r.Column8 , r.Column9 
    FROM v_table r1 INNER JOIN table1 r2          --<-- Alias should be different for 
    ON r1.ReferencingColumn = r2.ReferencingColumn     -- for both tables
    WHERE r1.columnName1  = 1         --<-- Use r1 or r2 alias here
     AND  r2.columnName2 = 2

  SET   @Error = @@ERROR;

IF (@Error <> 0) 
  ROLLBACK TRANSACTION 
ELSE 
  COMMIT TRANSACTION

Try..Catch 块

我建议你使用try catch 块,类似这样,它允许你在catch 块中使用错误函数并访问有关错误的详细信息。

BEGIN TRY

  BEGIN TRANSACTION
     DECLARE @Error INT;

        INSERT INTO table_chg (Column1, Column2)                                             
        SELECT r.Column8 , r.Column9 
        FROM v_table r1 INNER JOIN table1 r2          --<-- Alias should be different for 
        ON r1.ReferencingColumn = r2.ReferencingColumn     -- for both tables
        WHERE r1.columnName1  = 1         --<-- Use r1 or r2 alias here
         AND  r2.columnName2 = 2

  COMMIT TRANSACTION  

END TRY

BEGIN CATCH
 IF (@@TRANCOUNT > 0) 
  ROLLBACK TRANSACTION 

      SELECT ERROR_NUMBER() AS ERRORNUMBER,
             ERROR_MESSAGE() AS ERRORMESSAGE
             .
             ...... (All the other error functions)
END CATCH

【讨论】:

【参考方案2】:

这是你需要做的:

1 - 创建事务对象并使用Try-block

2 - 使用您的事务对象,执行SqlCommand.ExecuteNonQuery(插入) - 它将返回受影响的记录数。

3 - 使用您的事务对象,执行 SqlCommand.ExecuteNonQuery(存储过程) - 它将返回受影响的记录数和 [如果需要] 输出参数。

4 - 根据第 2 步和第 3 步的结果回滚或提交事务对象

5 - 不要这样做

sqlStr.Append("INSERT INTO table_chg (Column1, Column2) ")                                               
sqlStr.Append("SELECT r.Column8 , r.Column9 ").....

改为这样做:

Dim sql as String = _
    "INSERT INTO table_chg (Column1, Column2) " & _
    "SELECT r.Column8 , r.Column9 ".......

【讨论】:

你能解释一下#5 的原因吗?虽然原始海报没有这么说,但看起来他或她正在使用StringBuilder。改用串联字符串有什么好处? 字符串生成器在处理循环时是绝对必须的。但是,如果事先知道字符串,最好声明一个字符串。字符串生成器占用资源。如果您不初始化它,它会在您添加数据时自行调整大小,这将占用更多资源。在目前的情况下,使用string 比较合适。字符串是字节数组,字符串生成器是管理它的机制。在这里我们不需要它。 你是对的。我们不应该使用字符串生成器。但不幸的是,我被这个代码库困住了,而这段代码所做的只是用 Sql 代码构建字符串.....【参考方案3】:

你可以尝试使用TransactionScope

Using scope As TransactionScope = New TransactionScope
Try
'Here will be the DB call code
'Make some verifications if everyting was ok in DB
SCope.Complete()

Catch ex As Exception
End Try
End Using

我不会使用 SQL 中的 BEGIN TRY END TRY,因为如果发生错误,我不会一直捕获。例如,如果您调用了一个方法 throw 一个链接服务器并且在链接服务器上发生了错误,则 begin try end try 将不会捕获该错误:此外,“A TRY...CATCH 构造捕获所有严重程度高于 10 的执行错误不关闭数据库连接。”

如果您使用 TRANSACTIONSCOPE,您可以从 SQL 中删除 TRANSACTION。如果 scope.Complete() 由于脚本执行后的错误或验证而未被调用,则所有内容都将回滚。

【讨论】:

以上是关于在包含动态 sql 的事务中嵌入对存储过程的调用的主要内容,如果未能解决你的问题,请参考以下文章

sqlserver 存储过程 使用事务,说明在下边,求大神指教!

在一个事务中调用多个 SQL Server 存储过程

SQL事务的使用

面试题2

jpql sql在同一个事务中

从SQL代理作业调用时,工作存储过程失败