web-dev-qa-db-fra.com

Gestion des exceptions dans les procédures stockées appelées à l'aide de blocs insert-exec

J'ai une procédure stockée qui est appelée dans un bloc insert-exec:

insert into @t
    exec('test')

Comment gérer les exceptions générées dans la procédure stockée et continuer le traitement?

Le code suivant illustre le problème. Ce que je veux faire, c'est retourner 0 ou -1 selon le succès ou l'échec de l'appel interne de 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;

Mon problème est la return(-1). Le chemin du succès est bien.

Si je laisse de côté le bloc try/catch dans la procédure stockée, l'erreur est déclenchée et l'insertion échoue. Cependant, ce que je veux faire, c'est gérer l'erreur et renvoyer une valeur Nice.

Le code tel quel renvoie le message:

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.

C'est peut-être le pire message d'erreur que j'ai rencontré. Cela semble vraiment signifier "Vous n'avez pas géré d'erreur dans une transaction imbriquée".

Si je mets le if @@TRANCOUNT > 0, Je reçois le message:

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.

J'ai essayé de jouer avec les instructions de transaction begin/commit, mais rien ne semble fonctionner.

Alors, comment puis-je faire en sorte que ma procédure stockée gère les erreurs sans abandonner la transaction globale?

Modifier en réponse à Martin:

Le code d'appel réel est:

        declare @RetvalTable table (retval int);

        set @retval = -1;

        insert into @RetvalTable
            exec('

declare @retval int; exec @retval = '+ @ query +'; sélectionnez @retval ');

        select @retval = retval from @RetvalTable;

@query Est l'appel de procédure stockée. Le but est d'obtenir la valeur de retour de la procédure stockée. Si cela est possible sans insert (ou, plus précisément, sans démarrer une transaction), ce serait bien.

Je ne peux pas modifier les procédures stockées en général pour stocker la valeur dans une table, car il y en a trop. One d'entre eux échoue, et je peux modifier cela. Ma meilleure solution actuelle est quelque chose comme:

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;
10
Gordon Linoff

L'erreur dans la partie EXEC de l'instruction INSERT-EXEC Laisse votre transaction dans un état condamné.

Si vous PRINT out XACT_STATE() dans le bloc CATCH, il est défini sur -1.

Toutes les erreurs ne définissent pas l'état sur ceci. L'erreur de contrainte de vérification suivante est transmise au bloc catch et le INSERT réussit.

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; 

Ajout de cela au bloc CATCH

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

N'aide pas. Il donne l'erreur

Impossible d'utiliser l'instruction ROLLBACK dans une instruction INSERT-EXEC.

Je ne pense pas qu'il y ait moyen de se remettre d'une telle erreur une fois qu'elle s'est produite. Pour votre cas d'utilisation spécifique, vous n'avez cependant pas besoin de INSERT ... EXEC. Vous pouvez affecter la valeur de retour à une variable scalaire, puis l'insérer dans une instruction distincte.

DECLARE @RC INT;

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

INSERT INTO @t
VALUES      (@RC) 

Ou bien sûr, vous pouvez restructurer la procédure stockée appelée afin qu'elle ne déclenche pas du tout cette erreur.

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 ); 
13
Martin Smith