处理使用insert-exec块调用的存储过程中的异常


10

我有一个在insert-exec块中调用的存储过程:

insert into @t
    exec('test')

如何处理存储过程中生成的异常并仍继续处理?

以下代码说明了该问题。我想做的是根据内部exec()调用的成功或失败返回0或-1 :

alter procedure test -- or create
as
begin try
    declare @retval int;
    -- This code assumes that PrintMax exists already so this generates an error
    exec('create procedure PrintMax as begin print ''hello world'' end;')
    set @retval = 0;
    select @retval;
    return(@retval);
end try
begin catch
    -- if @@TRANCOUNT > 0 commit;
    print ERROR_MESSAGE();
    set @retval = -1;
    select @retval;
    return(@retval);
end catch;
go

declare @t table (i int);

insert into @t
    exec('test');

select *
from @t;

我的问题是return(-1)。成功的道路很好。

如果我在存储过程中遗漏了try / catch块,则会引发错误并且插入失败。但是,我想做的是处理错误并返回一个不错的值。

代码按原样返回消息:

Msg 3930, Level 16, State 1, Line 6
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.

这可能是我遇到的最糟糕的错误消息。这似乎真的意味着“您没有在嵌套事务中处理错误”。

如果输入if @@TRANCOUNT > 0,则会收到以下消息:

Msg 3916, Level 16, State 0, Procedure gordontest, Line 7
Cannot use the COMMIT statement within an INSERT-EXEC statement unless BEGIN TRANSACTION is used first.

我试过开始/提交事务语句,但似乎没有任何效果。

因此,如何让我的存储过程在不中断整个事务的情况下处理错误?

编辑以响应Martin:

实际的调用代码是:

        declare @RetvalTable table (retval int);

        set @retval = -1;

        insert into @RetvalTable
            exec('

声明@retval int; exec @retval ='+ @ query +'; 选择@retval');

        select @retval = retval from @RetvalTable;

@query存储过程调用在哪里。目的是从存储过程中获取返回值。如果没有insert(或者更具体地说,没有开始交易)这是可能的,那将很棒。

我通常不能修改存储过程以将值存储在表中,因为它们太多了。 其中之一失败了,我可以进行修改。我目前最好的解决方案是:

if (@StoredProcedure = 'sp_rep__post') -- causing me a problem
begin
    exec @retval = sp_rep__post;
end;
else
begin
    -- the code I'm using now
end;

您想在表变量中插入什么?无论如何,返回值不会插入那里。 declare @t table (i int);declare @RC int;exec @RC = test;insert into @t values (@RC);select * from @t;工作良好。
马丁·史密斯

@MartinSmith。。。代码真正起作用的方式更像 select @retval; return @retval是最后。如果您知道从动态存储过程调用中获取返回值的另一种方法,我很想知道。
Gordon Linoff

嗯,另一种方式是DECLARE @RC INT;EXEC sp_executesql N'EXEC @RC = test', N'@RC INT OUTPUT', @RC = @RC OUTPUT;insert into @t VALUES (@RC)
马丁·史密斯

@MartinSmith。。。我认为那行得通。我们花了半天的时间寻找硬件故障(“无法支持写入日志文件的操作”听起来像是硬件故障)和过去几个小时来尝试正确编写代码。变量替换是一个很好的答案。
Gordon Linoff

Answers:


13

语句EXEC部分中的错误INSERT-EXEC使您的事务处于注定的状态。

如果PRINTXACT_STATE()CATCH它被设置为块-1

并非所有错误都会将状态设置为此。以下检查约束错误传递到catch块并INSERT成功。

ALTER PROCEDURE test -- or create
AS
  BEGIN try
      DECLARE @retval INT;

      DECLARE @t TABLE(x INT CHECK (x = 0))

      INSERT INTO @t
      VALUES      (1)

      SET @retval = 0;

      SELECT @retval;

      RETURN( @retval );
  END try

  BEGIN catch
      PRINT XACT_STATE()

      PRINT ERROR_MESSAGE();

      SET @retval = -1;

      SELECT @retval;

      RETURN( @retval );
  END catch; 

将此添加到CATCH

 IF (XACT_STATE()) = -1
BEGIN
    ROLLBACK TRANSACTION;
END;

无济于事。它给出了错误

不能在INSERT-EXEC语句中使用ROLLBACK语句。

我认为一旦发生这种错误,没有任何方法可以恢复。对于您的特定用例,您还是不需要INSERT ... EXEC。您可以将返回值分配给标量变量,然后将其插入到单独的语句中。

DECLARE @RC INT;

EXEC sp_executesql
  N'EXEC @RC = test',
  N'@RC INT OUTPUT',
  @RC = @RC OUTPUT;

INSERT INTO @t
VALUES      (@RC) 

或者当然,您可以重组被调用的存储过程,以使它根本不会引发该错误。

DECLARE @RetVal INT = -1

IF OBJECT_ID('PrintMax', 'P') IS NULL
  BEGIN
      EXEC('create procedure PrintMax as begin print ''hello world'' end;')

      SET @RetVal = 0
  END

SELECT @RetVal;

RETURN( @RetVal ); 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.