Menangani pengecualian dalam prosedur tersimpan yang disebut menggunakan blok insert-exec

10

Saya memiliki prosedur tersimpan yang disebut dalam blok insert-exec:

insert into @t
    exec('test')

Bagaimana saya bisa menangani pengecualian yang dihasilkan dalam prosedur tersimpan dan masih melanjutkan pemrosesan?

Kode berikut menggambarkan masalah. Yang ingin saya lakukan adalah mengembalikan 0 atau -1 tergantung pada keberhasilan atau kegagalan exec()panggilan internal :

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;

Masalah saya adalah return(-1). Jalur sukses baik-baik saja.

Jika saya meninggalkan blok coba / tangkap dalam prosedur tersimpan, maka kesalahan dinaikkan dan insert gagal. Namun, yang ingin saya lakukan adalah menangani kesalahan dan mengembalikan nilai yang bagus.

Kode as is mengembalikan pesan:

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.

Ini mungkin pesan kesalahan terburuk yang saya temui. Tampaknya benar-benar berarti "Anda tidak menangani kesalahan dalam transaksi bersarang."

Jika saya memasukkan if @@TRANCOUNT > 0, maka saya mendapatkan pesan:

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.

Saya sudah mencoba bermain-main dengan memulai / melakukan pernyataan transaksi, tetapi sepertinya tidak ada yang berhasil.

Jadi, bagaimana saya bisa memiliki prosedur tersimpan menangani kesalahan tanpa membatalkan transaksi keseluruhan?

Edit dalam menanggapi Martin:

Kode panggilan sebenarnya adalah:

        declare @RetvalTable table (retval int);

        set @retval = -1;

        insert into @RetvalTable
            exec('

mendeklarasikan @retval int; exec @retval = '+ @ query +'; pilih @retval ');

        select @retval = retval from @RetvalTable;

Di mana @queryprosedur panggilan tersimpan? Tujuannya adalah untuk mendapatkan nilai balik dari prosedur tersimpan. Jika ini dimungkinkan tanpa insert(atau, lebih khusus, tanpa memulai transaksi), itu akan bagus.

Saya tidak bisa mengubah prosedur yang tersimpan secara umum untuk menyimpan nilai dalam tabel, karena ada terlalu banyak. Salah satunya gagal, dan saya bisa memodifikasinya. Solusi terbaik saya saat ini adalah sesuatu seperti:

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
sumber
Apa yang Anda coba masukkan ke dalam variabel tabel? Nilai pengembalian tidak dimasukkan di sana. declare @t table (i int);declare @RC int;exec @RC = test;insert into @t values (@RC);select * from @t;bekerja dengan baik.
Martin Smith
@MartinSmith. . . Cara kode tersebut bekerja lebih seperti select @retval; return @retvaldi bagian akhir. Jika Anda tahu cara lain untuk mendapatkan nilai kembali dari panggilan prosedur tersimpan dinamis, saya ingin tahu.
Gordon Linoff
Cara lain yang baik adalahDECLARE @RC INT;EXEC sp_executesql N'EXEC @RC = test', N'@RC INT OUTPUT', @RC = @RC OUTPUT;insert into @t VALUES (@RC)
Martin Smith
@MartinSmith. . . Saya pikir itu akan berhasil. Kami menghabiskan setengah hari mencari kesalahan perangkat keras ("tidak dapat mendukung operasi yang menulis ke file log" terdengar seperti kegagalan perangkat keras) dan beberapa jam terakhir mencoba untuk mendapatkan kode yang benar. Substitusi variabel adalah jawaban yang sangat baik.
Gordon Linoff

Jawaban:

13

Kesalahan pada EXECbagian dari INSERT-EXECpernyataan itu membuat transaksi Anda dalam keadaan hancur.

Jika Anda PRINTkeluar XACT_STATE()di CATCHblok itu diatur ke -1.

Tidak semua kesalahan akan mengatur keadaan ini. Galat kendala periksa berikut melewati ke blok tangkap dan INSERTberhasil.

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; 

Menambahkan ini ke CATCHblok

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

Tidak membantu Ini memberi kesalahan

Tidak dapat menggunakan pernyataan ROLLBACK dalam pernyataan INSERT-EXEC.

Saya tidak berpikir ada cara untuk pulih dari kesalahan seperti itu setelah itu terjadi. Namun untuk kasus penggunaan spesifik Anda, Anda tidak perlu INSERT ... EXECmelakukannya. Anda dapat menetapkan nilai kembali ke variabel skalar lalu menyisipkannya dalam pernyataan terpisah.

DECLARE @RC INT;

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

INSERT INTO @t
VALUES      (@RC) 

Atau tentu saja Anda dapat merestrukturisasi prosedur tersimpan yang disebut sehingga tidak menimbulkan kesalahan sama sekali.

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
sumber