Обробка винятків у збережених процедурах, званих за допомогою блоків 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 = '+ @ запит +'; виберіть @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.

Не всі помилки встановлять стан цього. Наступна помилка обмеження перевірки переходить до блоку спіймання і 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.