Обработка исключений в хранимых процедурах, вызываемых с использованием блоков insert-exec


10

У меня есть хранимая процедура, которая вызывается в блоке insert-exec:

insert into @t
    exec('test')

Как я могу обработать исключения, сгенерированные в хранимой процедуре, и при этом продолжить обработку?

Следующий код иллюстрирует проблему. То, что я хочу сделать, это вернуть 0 или -1 в зависимости от успеха или неудачи внутреннего exec()вызова:

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.

Я попытался поиграться с инструкциями начала / совершения транзакции, но, похоже, ничего не работает.

Итак, как мне сделать, чтобы моя хранимая процедура обрабатывала ошибки, не прерывая общую транзакцию?

Отредактируйте в ответ Мартину:

Фактический код вызова:

        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на конец. Если вы знаете другой способ получить возвращаемое значение из динамического вызова хранимой процедуры, я хотел бы знать.
Гордон Линофф

Ну, другой путь будетDECLARE @RC INT;EXEC sp_executesql N'EXEC @RC = test', N'@RC INT OUTPUT', @RC = @RC OUTPUT;insert into @t VALUES (@RC)
Мартин Смит

@MartinSmith. , , Я думаю, что это сработает. Мы потратили полдня на поиски аппаратного сбоя («не могу поддерживать операции, которые записывают в файл журнала», похоже на аппаратный сбой) и последние пару часов пытались получить правильный код. Подстановка переменных - отличный ответ.
Гордон Линофф

Ответы:


13

Ошибка в EXECчасти INSERT-EXECоператора оставляет вашу транзакцию в обреченном состоянии.

Если вас PRINTиз XACT_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;

Не помогает Выдает ошибку

Невозможно использовать инструкцию ROLLBACK внутри инструкции INSERT-EXEC.

Я не думаю, что есть какой-то способ восстановления после такой ошибки, как только она произошла. Для вашего конкретного случая использования вам все 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 ); 
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.