Gestire le eccezioni nelle stored procedure chiamate usando blocchi insert-exec


10

Ho una procedura memorizzata che viene chiamata in un blocco insert-exec:

insert into @t
    exec('test')

Come posso gestire le eccezioni generate nella procedura memorizzata e continuare comunque l'elaborazione?

Il seguente codice illustra il problema. Quello che voglio fare è restituire 0 o -1 a seconda dell'esito positivo o negativo della exec()chiamata interna :

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;

Il mio problema è il return(-1). La strada del successo va bene.

Se tralascio il blocco try / catch nella procedura memorizzata, l'errore viene generato e l'inserimento non riesce. Tuttavia, ciò che voglio fare è gestire l'errore e restituire un buon valore.

Il codice così com'è restituisce il messaggio:

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.

Questo è forse il peggior messaggio di errore che ho riscontrato. Sembra davvero significare "Non hai gestito un errore in una transazione nidificata".

Se inserisco il if @@TRANCOUNT > 0, allora ricevo il messaggio:

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.

Ho provato a giocare con le dichiarazioni di transazione start / commit, ma nulla sembra funzionare.

Quindi, come posso fare in modo che la mia procedura memorizzata gestisca gli errori senza interrompere la transazione complessiva?

Modifica in risposta a Martin:

Il codice chiamante effettivo è:

        declare @RetvalTable table (retval int);

        set @retval = -1;

        insert into @RetvalTable
            exec('

dichiarare @retval int; exec @retval = '+ @ query +'; seleziona @retval ');

        select @retval = retval from @RetvalTable;

Dov'è @queryla chiamata della procedura memorizzata. L'obiettivo è ottenere il valore restituito dalla procedura memorizzata. Se ciò è possibile senza un insert(o, più specificamente, senza avviare una transazione), sarebbe fantastico.

Non riesco a modificare le procedure memorizzate in generale per memorizzare il valore in una tabella, perché ce ne sono troppe. Uno di questi sta fallendo e posso modificarlo. La mia migliore soluzione attuale è qualcosa del tipo:

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;

Cosa stai cercando di inserire nella variabile della tabella? Il valore restituito non viene comunque inserito lì. declare @t table (i int);declare @RC int;exec @RC = test;insert into @t values (@RC);select * from @t;funziona bene.
Martin Smith,

@MartinSmith. . . Il modo in cui il codice funziona davvero è più simile select @retval; return @retvalalla fine. Se conosci un altro modo per ottenere il valore restituito da una chiamata di procedura memorizzata dinamica, mi piacerebbe saperlo.
Gordon Linoff,

Beh, un altro modo sarebbeDECLARE @RC INT;EXEC sp_executesql N'EXEC @RC = test', N'@RC INT OUTPUT', @RC = @RC OUTPUT;insert into @t VALUES (@RC)
Martin Smith,

@MartinSmith. . . Penso che funzionerà. Abbiamo trascorso mezza giornata alla ricerca di un errore hardware ("impossibile supportare operazioni che scrivono nel file di registro" suona come un errore hardware) e nelle ultime due ore abbiamo cercato di ottenere il codice giusto. La sostituzione delle variabili è una risposta eccellente.
Gordon Linoff,

Risposte:


13

L'errore nella EXECparte INSERT-EXECdell'estratto conto sta lasciando la transazione in uno stato condannato.

Se sei PRINTfuori XACT_STATE()nel CATCHblocco è impostato su -1.

Non tutti gli errori imposteranno lo stato su questo. Il seguente errore del vincolo di controllo passa al blocco catch e ha INSERTesito positivo.

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; 

Aggiungendo questo al CATCHblocco

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

Non aiuta Dà l'errore

Impossibile utilizzare l'istruzione ROLLBACK all'interno di un'istruzione INSERT-EXEC.

Non credo che ci sia modo di riprendersi da un simile errore una volta che si è verificato. INSERT ... EXECTuttavia, per il tuo caso d'uso specifico non è necessario . È possibile assegnare il valore restituito a una variabile scalare, quindi inserirlo in un'istruzione separata.

DECLARE @RC INT;

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

INSERT INTO @t
VALUES      (@RC) 

O, naturalmente, è possibile ristrutturare la stored procedure chiamata in modo che non generi affatto tale errore.

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 ); 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.