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

10

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' exec()appel interne :

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 le 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 belle valeur.

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;

Où se @querytrouve 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 de transaction), ce serait formidable.

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. L'un d'eux échoue et je peux le modifier. 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;
Gordon Linoff
la source
Qu'essayez-vous d'insérer dans la variable de table? La valeur de retour n'y est pas insérée de toute façon. declare @t table (i int);declare @RC int;exec @RC = test;insert into @t values (@RC);select * from @t;fonctionne bien.
Martin Smith
@MartinSmith. . . La façon dont le code fonctionne vraiment ressemble plus select @retval; return @retvalà la fin. Si vous connaissez une autre façon d'obtenir la valeur de retour d'un appel de procédure stockée dynamique, j'aimerais savoir.
Gordon Linoff
Une autre solution seraitDECLARE @RC INT;EXEC sp_executesql N'EXEC @RC = test', N'@RC INT OUTPUT', @RC = @RC OUTPUT;insert into @t VALUES (@RC)
Martin Smith
@MartinSmith. . . Je pense que cela fonctionnera. Nous avons passé la moitié de la journée à rechercher une défaillance matérielle («ne peut pas prendre en charge les opérations qui écrivent dans le fichier journal» sonne comme une défaillance matérielle) et les dernières heures à essayer d'obtenir le bon code. La substitution variable est une excellente réponse.
Gordon Linoff

Réponses:

13

L'erreur dans la EXECpartie de la INSERT-EXECdéclaration laisse votre transaction dans un état condamné.

Si vous PRINTsortez XACT_STATE()dans le CATCHbloc, il est réglé sur -1.

Toutes les erreurs ne définissent pas l'état sur ceci. L'erreur de contrainte de vérification suivante passe par le bloc catch et INSERTré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 CATCHbloc

 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 ... EXECtoute façon. 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 ); 
Martin Smith
la source