Behandlung von Ausnahmen in gespeicherten Prozeduren, die mit insert-exec-Blöcken aufgerufen werden

10

Ich habe eine gespeicherte Prozedur, die in einem Insert-Exec-Block aufgerufen wird:

insert into @t
    exec('test')

Wie kann ich mit Ausnahmen umgehen, die in der gespeicherten Prozedur generiert wurden, und trotzdem die Verarbeitung fortsetzen?

Der folgende Code veranschaulicht das Problem. Ich möchte je nach Erfolg oder Misserfolg des internen exec()Anrufs 0 oder -1 zurückgeben :

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;

Mein Problem ist das return(-1). Der Erfolgspfad ist in Ordnung.

Wenn ich den try / catch-Block in der gespeicherten Prozedur weglasse, wird der Fehler ausgelöst und das Einfügen schlägt fehl. Ich möchte jedoch den Fehler behandeln und einen schönen Wert zurückgeben.

Der Code wie er ist gibt die Nachricht zurück:

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.

Dies ist vielleicht die schlimmste Fehlermeldung, die mir begegnet ist. Es scheint wirklich zu bedeuten, dass Sie in einer verschachtelten Transaktion keinen Fehler behandelt haben.

Wenn ich das eingebe if @@TRANCOUNT > 0, bekomme ich die Nachricht:

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.

Ich habe versucht, mit begin / commit-Transaktionsanweisungen herumzuspielen, aber nichts scheint zu funktionieren.

Wie kann meine gespeicherte Prozedur Fehler behandeln, ohne die gesamte Transaktion abzubrechen?

Bearbeiten als Antwort auf Martin:

Der eigentliche Aufrufcode lautet:

        declare @RetvalTable table (retval int);

        set @retval = -1;

        insert into @RetvalTable
            exec('

deklariere @retval int; exec @retval = '+ @ query +'; wähle @retval ');

        select @retval = retval from @RetvalTable;

Wo @queryist der Aufruf der gespeicherten Prozedur? Ziel ist es, den Rückgabewert aus der gespeicherten Prozedur zu erhalten. Wenn dies ohne insert(oder genauer gesagt ohne Starten einer Transaktion) möglich ist, wäre das großartig.

Ich kann die gespeicherten Prozeduren im Allgemeinen nicht ändern, um den Wert in einer Tabelle zu speichern, da zu viele davon vorhanden sind. Einer von ihnen schlägt fehl, und das kann ich ändern. Meine derzeit beste Lösung ist ungefähr:

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
quelle
Was versuchen Sie in die Tabellenvariable einzufügen? Der Rückgabewert wird dort sowieso nicht eingefügt. declare @t table (i int);declare @RC int;exec @RC = test;insert into @t values (@RC);select * from @t;funktioniert gut.
Martin Smith
@ MartinSmith. . . Die Art und Weise, wie der Code wirklich funktioniert, ist eher wie select @retval; return @retvalam Ende. Wenn Sie einen anderen Weg kennen, um den Rückgabewert von einem dynamischen Aufruf einer gespeicherten Prozedur zu erhalten, würde ich gerne wissen.
Gordon Linoff
Nun, ein anderer Weg wäreDECLARE @RC INT;EXEC sp_executesql N'EXEC @RC = test', N'@RC INT OUTPUT', @RC = @RC OUTPUT;insert into @t VALUES (@RC)
Martin Smith
@ MartinSmith. . . Ich denke das wird funktionieren. Wir haben den halben Tag damit verbracht, nach Hardwarefehlern zu suchen ("Vorgänge, die in die Protokolldatei schreiben, können nicht unterstützt werden", klingt nach einem Hardwarefehler), und in den letzten Stunden versucht, den Code richtig zu machen. Die variable Substitution ist eine hervorragende Antwort.
Gordon Linoff

Antworten:

13

Der Fehler in dem EXECTeil der INSERT-EXECAnweisung führt dazu, dass Ihre Transaktion in einem zum Scheitern verurteilten Zustand bleibt.

Wenn Sie PRINTheraus XACT_STATE()in dem CATCHBlock wird eingestellt -1.

Nicht alle Fehler setzen den Status auf diesen Wert. Der folgende Überprüfungsbeschränkungsfehler geht bis zum catch-Block und der ist INSERTerfolgreich.

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; 

Hinzufügen zum CATCHBlock

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

Hilft nicht. Es gibt den Fehler

Die ROLLBACK-Anweisung kann nicht in einer INSERT-EXEC-Anweisung verwendet werden.

Ich glaube nicht, dass es eine Möglichkeit gibt, einen solchen Fehler zu beheben, wenn er einmal aufgetreten ist. Für Ihren speziellen Anwendungsfall benötigen Sie dies jedoch INSERT ... EXECohnehin nicht . Sie können den Rückgabewert einer skalaren Variablen zuweisen und diesen dann in eine separate Anweisung einfügen.

DECLARE @RC INT;

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

INSERT INTO @t
VALUES      (@RC) 

Oder Sie können die aufgerufene gespeicherte Prozedur natürlich so umstrukturieren, dass dieser Fehler überhaupt nicht auftritt.

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
quelle