Cara memunculkan kembali pengecualian yang sama di SQL Server

86

Saya ingin menampilkan kembali pengecualian yang sama di SQL Server yang baru saja terjadi di blok percobaan saya. Saya dapat memberikan pesan yang sama tetapi saya ingin memberikan kesalahan yang sama.

BEGIN TRANSACTION
    BEGIN TRY
        INSERT INTO Tags.tblDomain (DomainName, SubDomainId, DomainCode, Description)
            VALUES(@DomainName, @SubDomainId, @DomainCode, @Description)
        COMMIT TRANSACTION
    END TRY
    
    BEGIN CATCH
        declare @severity int; 
        declare @state int;

        select @severity=error_severity(), @state=error_state();

        RAISERROR(@@Error,@ErrorSeverity,@state);
        ROLLBACK TRANSACTION
    END CATCH

RAISERROR(@@Error, @ErrorSeverity, @state);

Baris ini akan menunjukkan kesalahan, tetapi saya ingin fungsionalitas seperti itu. Ini menimbulkan kesalahan dengan nomor kesalahan 50000, tetapi saya ingin nomor kesalahan dilemparkan yang saya lewati @@error,

Saya ingin menangkap kesalahan ini tidak di bagian depan.

yaitu

catch (SqlException ex)
{
    if ex.number==2627
    MessageBox.show("Duplicate value cannot be inserted");
}

Saya ingin fungsi ini. yang tidak dapat dicapai dengan menggunakanraiseerror . Saya tidak ingin memberikan pesan kesalahan khusus di bagian belakang.

RAISEERROR harus mengembalikan kesalahan yang disebutkan di bawah ini ketika saya melewati ErrorNo untuk dilempar ke dalam tangkapan

Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,

Baris 14 Pelanggaran batasan UNIQUE KEY 'UK_DomainCode'. Tidak dapat memasukkan kunci duplikat dalam objek 'Tags.tblDomain'. Pernyataan tersebut telah dihentikan.

EDIT:

Apa yang bisa menjadi kekurangan dari tidak menggunakan blok try catch jika saya ingin pengecualian ditangani di frontend mengingat prosedur tersimpan berisi banyak kueri yang perlu dijalankan?

Shantanu Gupta
sumber

Jawaban:

120

Berikut adalah contoh kode bersih yang berfungsi penuh untuk mengembalikan serangkaian pernyataan jika terjadi kesalahan dan melaporkan pesan kesalahan.

begin try
    begin transaction;

    ...

    commit transaction;
end try
begin catch
    if @@trancount > 0 rollback transaction;
    throw;
end catch

Sebelum SQL 2012

begin try
    begin transaction;
    
    ...
    
    commit transaction;
end try
begin catch
    declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
    select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
    if @@trancount > 0 rollback transaction;
    raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
end catch
Ben Gripka
sumber
8
Saya menggunakan ini di tengah prosedur tersimpan, dan menemukan bahwa itu akan terus dijalankan setelahnya raiserror, yang berbeda dari bagaimana c # keluar setelah a throw. Jadi saya menambahkan bagian returndalam catchkarena saya ingin mencocokkan perilaku itu.
Brian J
@BogdanBogdanov Saya membatalkan suntingan Anda karena inti dari kode ini adalah menjadi minimal dan tidak mengurangi kode sebenarnya yang dimasukkan sebagai pengganti ...
Ben Gripka
Oke, tidak masalah, @Ben Gripka. Saya mencoba membuatnya lebih mudah dibaca di layar. Terima kasih telah menunjukkan alasan melakukan rollback.
Bogdan Bogdanov
1
@ BrianJ: biasanya, apakah eksekusi berhenti atau tidak tergantung pada tingkat keparahan kesalahan asli. Jika tingkat keparahan> = 11 maka eksekusi harus dihentikan. Aneh sekali, karena raiserror di dalam blok catch dengan severity> = 11 tidak menghentikan eksekusi lagi. Pengamatan Anda sangat bagus dan itu menunjukkan bagaimana otak-mati adalah sql server, setidaknya 2008r2. Versi yang lebih baru tampak lebih baik.
costa
1
@costa Lihat RAISERROR()dok . Keparahan ≥11 hanya melompat ke satu CATCHblok jika berada di dalam TRYblok. Jadi Anda harus memiliki BEGIN TRY…END CATCHsekitar kode jika Anda ingin RAISERROR()mempengaruhi kontrol aliran.
binki
137

SQL 2012 memperkenalkan pernyataan lemparan:

http://msdn.microsoft.com/en-us/library/ee677615.aspx

Jika pernyataan THROW ditentukan tanpa parameter, itu harus muncul di dalam blok CATCH. Hal ini menyebabkan pengecualian yang tertangkap akan dimunculkan.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH
Michael
sumber
2
Hati-hati, sepertinya solusi ini hanya berfungsi dari sql server 2012 dan yang lebih baru: msdn.microsoft.com/en-us/library/ee677615.aspx
Adi
3
@BogdanBogdanov Agar Anda dapat mencatat kesalahan, mungkin menangani beberapa situasi, tetapi jika Anda tidak bisa maka Anda ingin mengembalikan kesalahan sehingga percobaan / penangkapan yang lebih tinggi kemudian dapat memiliki kesempatan untuk menanganinya
Robert McKee
Ya, @Robert McKee. Saya mencari tahu itu. Maaf, itu lupa menghapus komentar ini.
Bogdan Bogdanov
4
Titik koma di ROLLBACKtelepon itu penting! Tanpanya Anda mungkin mendapatkan file SQLException: Cannot roll back THROW.
idontevenseethecode
5

Melempar kembali di dalam blok CATCH (kode pra-SQL2012, gunakan pernyataan THROW untuk SQL2012 dan yang lebih baru):

DECLARE
    @ErrorMessage nvarchar(4000) = ERROR_MESSAGE(),
    @ErrorNumber int = ERROR_NUMBER(),
    @ErrorSeverity int = ERROR_SEVERITY(),
    @ErrorState int = ERROR_STATE(),
    @ErrorLine int = ERROR_LINE(),
    @ErrorProcedure nvarchar(200) = ISNULL(ERROR_PROCEDURE(), '-');
SELECT @ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 'Message: ' + @ErrorMessage;
RAISERROR (@ErrorMessage, @ErrorSeverity, 1, @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine)
nzeemin.dll
sumber
4

Saya pikir pilihan Anda adalah:

  • Jangan menangkap kesalahan (biarkan menggelembung)
  • Angkat yang khusus

Pada titik tertentu, SQL mungkin akan memperkenalkan perintah reraise, atau kemampuan untuk menangkap kesalahan tertentu saja. Tapi untuk saat ini, gunakan solusi. Maaf.

Rob Farley
sumber
7
di sql 2012 Anda dapat mengajukan kembali pengecualian menggunakan kata kunci
THROW
5
Iya. Tentu saja, itu tidak tersedia saat pertanyaan ini diajukan.
Rob Farley
Akan lebih penting untuk menangkap dan melempar kesalahan baru daripada tidak menangkapnya dan membiarkannya 'meluap' karena Anda kemungkinan akan memerlukan beberapa kegiatan pembersihan, tindakan korektif, dan penutupan untuk menangani pengecualian dengan benar. Contoh nyata adalah menutup dan membuang kursor. Contoh lain mungkin menjalankan prosedur logging, atau mengatur ulang beberapa data.
Antony Booth
1

Anda tidak dapat: hanya mesin yang dapat melakukan kesalahan kurang dari 50000. Yang dapat Anda lakukan hanyalah membuat pengecualian yang terlihat seperti itu ...

Silakan lihat jawaban saya di sini

Penanya di sini menggunakan transaksi sisi klien untuk melakukan apa yang dia inginkan yang menurut saya agak konyol ...

gbn
sumber
0

Oke, ini solusinya ... :-)

DECLARE @Error_Number INT
BEGIN TRANSACTION 
    BEGIN TRY
    INSERT INTO Test(Id, Name) VALUES (newID(),'Ashish') 
    /* Column 'Name' has unique constraint on it*/
    END TRY
    BEGIN CATCH

            SELECT ERROR_NUMBER()
            --RAISERROR (@ErrorMessage,@Severity,@State)
            ROLLBACK TRAN
    END CATCH

Jika Anda mencatat blok tangkap, Ini tidak meningkatkan kesalahan tetapi mengembalikan nomor kesalahan sebenarnya (dan juga akan mengembalikan transaksi). Sekarang dalam kode .NET Anda, alih-alih menangkap pengecualian, jika Anda menggunakan ExecuteScalar (), Anda mendapatkan nomor kesalahan aktual yang Anda inginkan dan menunjukkan nomor yang sesuai.

int errorNumber=(int)command.ExecuteScalar();
if(errorNumber=<SomeNumber>)
{
    MessageBox.Show("Some message");
}

Semoga ini membantu,

EDIT: - Sekadar catatan, Jika Anda ingin mendapatkan jumlah record yang terpengaruh dan mencoba menggunakan ExecuteNonQuery, solusi di atas mungkin tidak berfungsi untuk Anda. Jika tidak, saya pikir itu akan sesuai dengan apa yang Anda butuhkan. Biarkan aku tahu.

Ashish Gupta
sumber
@Ashish Gupta: Terima kasih untuk bantuan, Tetapi saya perlu pengecualian untuk dilemparkan dari database ke frontend, jika tidak saya memiliki banyak opsi terbuka seperti print error_number (), kembalikan error_number dan saran 1 u
Shantanu Gupta
0

Cara untuk menghentikan eksekusi dalam prosedur tersimpan setelah kesalahan terjadi dan mengembalikan kesalahan ke program pemanggil adalah dengan mengikuti setiap pernyataan yang mungkin menimbulkan kesalahan dengan kode ini:

If @@ERROR > 0
Return

Saya sendiri terkejut saat mengetahui bahwa eksekusi dalam prosedur tersimpan dapat berlanjut setelah terjadi kesalahan - tidak menyadari hal ini dapat menyebabkan beberapa bug sulit dilacak.

Jenis kesalahan penanganan paralel (pra .Net) Visual Basic 6. Menantikan perintah Lempar di SQL Server 2012.

Chuck Bevitt
sumber
0

Mengingat bahwa Anda belum pindah ke tahun 2012, salah satu cara untuk mengimplementasikan penggelembungan kode kesalahan asli adalah dengan menggunakan bagian pesan teks dari pengecualian yang Anda lemparkan (ulang) dari blok catch. Ingatlah bahwa ini dapat berisi beberapa struktur, misalnya, teks XML untuk kode pemanggil Anda untuk diurai dalam blok catch-nya.

Yuri Makassiouk
sumber
0

Anda juga dapat membuat prosedur penyimpanan pembungkus untuk skenario tersebut ketika Anda ingin pernyataan SQL dijalankan dalam transaksi dan memasukkan kesalahan ke kode Anda.

CREATE PROCEDURE usp_Execute_SQL_Within_Transaction
(
    @SQL nvarchar(max)
)
AS

SET NOCOUNT ON

BEGIN TRY
    BEGIN TRANSACTION
        EXEC(@SQL)
    COMMIT TRANSACTION
END TRY

BEGIN CATCH
    DECLARE @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int
    SELECT @ErrorMessage = N'Error Number: ' + CONVERT(nvarchar(5), ERROR_NUMBER()) + N'. ' + ERROR_MESSAGE() + ' Line ' + CONVERT(nvarchar(5), ERROR_LINE()), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE()
    ROLLBACK TRANSACTION
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH

GO

-- Test it
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1/0; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'EXEC usp_Another_SP'
Sergey
sumber
-2

Dari sudut pandang desain, apa gunanya memberikan pengecualian dengan nomor kesalahan asli dan pesan khusus? Untuk beberapa hal itu merusak kontrak antarmuka antara aplikasi dan database. Jika Anda ingin menangkap kesalahan asli dan menanganinya dalam kode yang lebih tinggi, jangan menanganinya di database. Kemudian saat Anda menemukan pengecualian, Anda dapat mengubah pesan yang disajikan kepada pengguna menjadi apa pun yang Anda inginkan. Saya tidak akan melakukannya, karena itu membuat kode database Anda hmm 'tidak benar'. Seperti yang dikatakan orang lain, Anda harus menentukan satu set kode kesalahan Anda sendiri (di atas 50000) dan membuangnya. Kemudian Anda dapat menangani masalah integritas ('Nilai duplikat tidak diperbolehkan') secara terpisah dari potensi masalah bisnis - 'Kode pos tidak valid', 'Tidak ada baris yang cocok dengan kriteria', dan seterusnya.

Piotr Rodak
sumber
9
Apa gunanya melempar pengecualian dengan nomor kesalahan asli dan pesan khusus? Misalkan Anda ingin menangani satu atau dua kesalahan spesifik (diharapkan) langsung di blok catch dan meninggalkan sisanya untuk layer yang lebih tinggi. Dengan demikian, Anda harus dapat menampilkan kembali pengecualian yang tidak Anda tangani ... sebaiknya tanpa harus menggunakan pelaporan dan penanganan kesalahan dengan cara khusus lainnya.
Jenda
1
Selain apa yang dijelaskan @Jenda, saya suka menggunakan try-catch untuk memastikan bahwa eksekusi kode tidak berlanjut setelah pengecualian, seperti yang dilakukan di C #:, di try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }mana throw;hanya akan memunculkan pengecualian asli, dengan konteks aslinya.
R. Schreurs
Saya menangkap kesalahan dan membuang kembali pesan kesalahan khusus dalam SQL untuk menambahkan detail yang menjelaskan baris mana kesalahan terjadi atau detail lainnya (seperti data yang mencoba dimasukkan) untuk membantu saya melacak kesalahan nanti.
Russell Hankins