SQL Server - menghentikan atau menghancurkan eksekusi skrip SQL

325

Apakah ada cara untuk segera menghentikan eksekusi skrip SQL di SQL server, seperti perintah "break" atau "exit"?

Saya memiliki skrip yang melakukan beberapa validasi dan pencarian sebelum mulai memasukkan, dan saya ingin berhenti jika salah satu validasi atau pencarian gagal.

Andy White
sumber

Jawaban:

371

The raiserror Metode

raiserror('Oh no a fatal error', 20, -1) with log

Ini akan menghentikan koneksi, sehingga menghentikan sisa skrip agar tidak berjalan.

Perhatikan bahwa tingkat keparahan 20 atau lebih tinggi dan WITH LOG opsi diperlukan untuk bekerja dengan cara ini.

Ini bahkan berfungsi dengan pernyataan GO, mis.

print 'hi'
go
raiserror('Oh no a fatal error', 20, -1) with log
go
print 'ho'

Akan memberi Anda output:

hi
Msg 2745, Level 16, State 2, Line 1
Process ID 51 has raised user error 50000, severity 20. SQL Server is terminating this process.
Msg 50000, Level 20, State 1, Line 1
Oh no a fatal error
Msg 0, Level 20, State 0, Line 0
A severe error occurred on the current command.  The results, if any, should be discarded.

Perhatikan bahwa 'ho' tidak dicetak.

CAVEAT:

  • Ini hanya berfungsi jika Anda masuk sebagai admin (peran 'sysadmin'), dan juga membuat Anda tidak memiliki koneksi database.
  • Jika Anda TIDAK masuk sebagai admin, panggilan RAISEERROR () itu sendiri akan gagal dan skrip akan terus dijalankan .
  • Ketika dipanggil dengan sqlcmd.exe, kode keluar 2745 akan dilaporkan.

Referensi: http://www.mydatabasesupport.com/forums/ms-sqlserver/174037-sql-server-2000-abort-whole-script.html#post761334

Metode noexec

Metode lain yang berfungsi dengan pernyataan GO adalah set noexec on. Ini menyebabkan sisa skrip dilewati. Itu tidak mengakhiri koneksi, tetapi Anda harus noexecmematikan lagi sebelum perintah apa pun akan dijalankan.

Contoh:

print 'hi'
go

print 'Fatal error, script will not continue!'
set noexec on

print 'ho'
go

-- last line of the script
set noexec off -- Turn execution back on; only needed in SSMS, so as to be able 
               -- to run this script again in the same session.
Blorgbeard keluar
sumber
14
Itu luar biasa! Ini sedikit pendekatan "tongkat besar", tetapi ada kalanya Anda benar-benar membutuhkannya. Perhatikan bahwa ini membutuhkan tingkat keparahan 20 (atau lebih tinggi) dan "DENGAN LOG".
Rob Garrison
5
Perhatikan bahwa dengan metode noexec sisa skrip masih ditafsirkan, sehingga Anda masih akan mendapatkan kesalahan waktu kompilasi, seperti kolom tidak ada. Jika Anda ingin secara kondisional menangani perubahan skema yang diketahui melibatkan kolom yang hilang dengan melewatkan beberapa kode, satu-satunya cara saya tahu untuk melakukannya adalah dengan menggunakan: r dalam mode sqlcommand untuk referensi file eksternal.
David Eison
20
Hal noexec itu bagus. Terima kasih banyak!
Gaspa79
2
"Ini akan memutuskan koneksi" - tampaknya tidak, setidaknya itulah yang saya lihat.
jcollum
6
Saya mencoba metode ini dan tidak mendapatkan hasil yang benar ketika saya menyadari ... Hanya ada satu E di dalam raiserror ...
bobkingof12vs
187

Cukup gunakan RETURN (ini akan bekerja di dalam dan di luar prosedur tersimpan).

Gordon Bell
sumber
2
Untuk beberapa alasan, saya berpikir bahwa pengembalian tidak berfungsi dalam skrip, tetapi saya baru mencobanya, dan ternyata berhasil! Terima kasih
Andy White
4
Dalam skrip, Anda tidak bisa melakukan RETURN dengan nilai seperti yang Anda bisa dalam prosedur tersimpan, tetapi Anda bisa melakukan RETURN.
Rob Garrison
53
Tidak hanya berakhir sampai GO berikutnya Batch berikutnya (setelah GO) akan berjalan seperti biasa
mortb
2
berbahaya untuk diasumsikan karena akan terus berlanjut setelah GO berikutnya.
Justin
1
GO adalah terminator atau pembatas skrip; itu bukan kode SQL. GO hanyalah instruksi untuk klien yang Anda gunakan untuk mengirim perintah ke mesin database bahwa skrip baru dimulai setelah pembatas GO.
Reversed Engineer
50

Jika Anda dapat menggunakan mode SQLCMD, maka mantra

:on error exit

(TERMASUK usus besar) akan menyebabkan RAISERROR untuk benar-benar menghentikan skrip. Misalnya,

:on error exit

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SOMETABLE]') AND type in (N'U')) 
    RaisError ('This is not a Valid Instance Database', 15, 10)
GO

print 'Keep Working'

akan menampilkan:

Msg 50000, Level 15, State 10, Line 3
This is not a Valid Instance Database
** An error was encountered during execution of batch. Exiting.

dan bets akan berhenti. Jika mode SQLCMD tidak diaktifkan, Anda akan mendapatkan kesalahan parse tentang titik dua. Sayangnya, ini tidak sepenuhnya anti-peluru seolah-olah skrip dijalankan tanpa berada dalam mode SQLCMD, SQL Managment Studio melaju melewati kesalahan waktu bahkan parse! Namun, jika Anda menjalankannya dari baris perintah, ini tidak masalah.

Sglasses
sumber
4
Komentar yang bagus, terima kasih. Saya akan menambahkan bahwa dalam mode SSMS SQLCmd beralih di bawah menu Permintaan.
David Peters
ini berguna - artinya Anda tidak memerlukan opsi -b saat menjalankan
JonnyRaa
2
lalu mantera ... tapi bagaimana cara melemparkan Magic Missle ?!
JJS
1
sempurna. tidak memerlukan sysadmin hak pengguna ekstra
Pac0
21

Saya tidak akan menggunakan RAISERROR- SQL memiliki pernyataan IF yang dapat digunakan untuk tujuan ini. Lakukan validasi dan pencarian Anda dan atur variabel lokal, lalu gunakan nilai variabel dalam pernyataan IF untuk membuat sisipan bersyarat.

Anda tidak perlu memeriksa hasil variabel dari setiap tes validasi. Anda biasanya dapat melakukan ini dengan hanya satu variabel bendera untuk mengonfirmasi semua kondisi yang diteruskan:

declare @valid bit

set @valid = 1

if -- Condition(s)
begin
  print 'Condition(s) failed.'
  set @valid = 0
end

-- Additional validation with similar structure

-- Final check that validation passed
if @valid = 1
begin
  print 'Validation succeeded.'

  -- Do work
end

Meskipun validasi Anda lebih kompleks, Anda hanya perlu beberapa variabel flag untuk disertakan dalam pemeriksaan akhir Anda.

Dave Swersky
sumber
Ya, saya menggunakan IF di bagian lain dari skrip, tetapi saya tidak ingin harus memeriksa setiap variabel lokal sebelum saya mencoba melakukan penyisipan. Saya lebih suka menghentikan seluruh skrip, dan memaksa pengguna untuk memeriksa input. (Ini hanya naskah cepat dan kotor)
Andy White
4
Saya tidak yakin mengapa jawaban ini telah ditandai karena secara teknis itu benar, hanya saja tidak apa yang "ingin" dilakukan oleh poster itu.
John Sansom
Apakah mungkin untuk memiliki beberapa blok dalam Begin..End? PERNYATAAN Arti; PERGILAH; PERNYATAAN; PERGILAH; dll dll? Saya mendapatkan kesalahan dan saya rasa itu mungkin menjadi alasannya.
Nenotlep
3
Ini jauh lebih dapat diandalkan daripada RAISERROR, terutama jika Anda tidak tahu siapa yang akan menjalankan skrip dan dengan hak istimewa apa.
Cypher
@John Sansom: Satu-satunya masalah yang saya lihat di sini adalah bahwa pernyataan IF tidak berfungsi jika Anda mencoba untuk bercabang melalui pernyataan GO. Ini adalah masalah besar jika skrip Anda bergantung pada pernyataan GO (mis. Pernyataan DDL). Berikut adalah contoh yang berfungsi tanpa pernyataan go pertama:declare @i int = 0; if @i=0 begin select '1st stmt in IF block' go end else begin select 'ELSE here' end go
James Jensen
16

Di SQL 2012+, Anda bisa menggunakan THROW .

THROW 51000, 'Stopping execution because validation failed.', 0;
PRINT 'Still Executing'; -- This doesn't execute with THROW

Dari MSDN:

Meningkatkan pengecualian dan mentransfer eksekusi ke blok CATCH dari TRY ... CATCH construct ... Jika sebuah TRY ... CATCH konstruksi tidak tersedia, sesi berakhir. Nomor baris dan prosedur di mana pengecualian dinaikkan diatur. Tingkat keparahan diatur ke 16.

Jordan Parker
sumber
1
THROW dimaksudkan untuk menggantikan RAISERROR, tetapi Anda tidak dapat mencegah batch berikutnya dalam file skrip yang sama dengannya.
NReilingh
@NReilingh benar. Di situlah jawaban Blorgbeard adalah satu-satunya solusi. Memang membutuhkan sysadmin (tingkat keparahan 20), dan itu cukup berat jika tidak ada banyak batch dalam skrip.
Jordan Parker
2
atur xact abort on jika Anda ingin membatalkan transkasi saat ini juga.
nurettin
13

Saya berhasil memperluas solusi on / off noexec dengan transaksi untuk menjalankan script dengan cara apa pun atau tidak sama sekali.

set noexec off

begin transaction
go

<First batch, do something here>
go
if @@error != 0 set noexec on;

<Second batch, do something here>
go
if @@error != 0 set noexec on;

<... etc>

declare @finished bit;
set @finished = 1;

SET noexec off;

IF @finished = 1
BEGIN
    PRINT 'Committing changes'
    COMMIT TRANSACTION
END
ELSE
BEGIN
    PRINT 'Errors occured. Rolling back changes'
    ROLLBACK TRANSACTION
END

Rupanya kompiler "memahami" variabel @finished di IF, bahkan jika ada kesalahan dan eksekusi dinonaktifkan. Namun, nilainya hanya 1 jika eksekusi tidak dinonaktifkan. Karenanya saya dapat dengan baik melakukan atau mengembalikan transaksi yang sesuai.

Tz_
sumber
Saya tidak mengerti. Saya mengikuti instruksi. Saya memasukkan SQL berikut setelah setiap GO. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Tetapi eksekusi tidak pernah berhenti, dan saya berakhir dengan tiga kesalahan "Rolling back Transaction". Ada ide?
user1161391
12

Anda bisa membungkus pernyataan SQL Anda dalam lingkaran WHILE dan menggunakan BREAK jika diperlukan

WHILE 1 = 1
BEGIN
   -- Do work here
   -- If you need to stop execution then use a BREAK


    BREAK; --Make sure to have this break at the end to prevent infinite loop
END
Jon Erickson
sumber
5
Saya agak suka tampilan ini, sepertinya sedikit lebih bagus daripada meningkatkan kesalahan. Jelas tidak ingin melupakan istirahat di akhir!
Andy White
1
Anda juga bisa menggunakan variabel dan langsung mengaturnya di bagian atas loop untuk menghindari "split". DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; ENDLebih banyak verbose, tapi sih, ini TSQL ;-)
Inilah cara sebagian orang melakukan goto, tetapi lebih membingungkan untuk diikuti daripada goto.
nurettin
Pendekatan ini melindungi dari GO sesekali yang tidak terduga. Menghargai.
it3xl
10

Anda dapat mengubah alur eksekusi menggunakan pernyataan GOTO :

IF @ValidationResult = 0
BEGIN
    PRINT 'Validation fault.'
    GOTO EndScript
END

/* our code */

EndScript:
Charlie
sumber
2
menggunakan goto adalah cara yang dapat diterima untuk menangani pengecualian. Mengurangi jumlah variabel dan bersarang dan tidak menyebabkan pemutusan. Ini mungkin lebih baik daripada penanganan kuno yang memungkinkan scripting SQL Server.
Antonio Drusin
Sama seperti SEMUA saran lainnya di sini, ini tidak berfungsi jika "kode kami" berisi pernyataan "GO".
Mike Gledhill
9

Metode Sglasses refinig lebih lanjut, garis-garis di atas memaksa penggunaan mode SQLCMD, dan baik meredam skirpt jika tidak menggunakan mode SQLCMD atau menggunakan :on error exituntuk keluar dari kesalahan apa pun
CONTEXT_INFO digunakan untuk melacak keadaan.

SET CONTEXT_INFO  0x1 --Just to make sure everything's ok
GO 
--treminate the script on any error. (Requires SQLCMD mode)
:on error exit 
--If not in SQLCMD mode the above line will generate an error, so the next line won't hit
SET CONTEXT_INFO 0x2
GO
--make sure to use SQLCMD mode ( :on error needs that)
IF CONTEXT_INFO()<>0x2 
BEGIN
    SELECT CONTEXT_INFO()
    SELECT 'This script must be run in SQLCMD mode! (To enable it go to (Management Studio) Query->SQLCMD mode)\nPlease abort the script!'
    RAISERROR('This script must be run in SQLCMD mode! (To enable it go to (Management Studio) Query->SQLCMD mode)\nPlease abort the script!',16,1) WITH NOWAIT 
    WAITFOR DELAY '02:00'; --wait for the user to read the message, and terminate the script manually
END
GO

----------------------------------------------------------------------------------
----THE ACTUAL SCRIPT BEGINS HERE-------------
jaraics
sumber
2
Ini adalah satu-satunya cara saya menemukan untuk mengatasi kegilaan SSMS karena tidak dapat membatalkan naskah. Tapi saya menambahkan 'SET NOEXEC OFF' di awal, dan 'SET NOEXEC ON' jika tidak dalam mode SQLCMD, jika tidak skrip yang sebenarnya akan terus berjalan kecuali Anda meningkatkan kesalahan pada level 20 dengan log.
Mark Sowul
8

Apakah ini prosedur yang tersimpan? Jika demikian, saya pikir Anda bisa melakukan Pengembalian, seperti "Kembali NULL";

mtazva
sumber
Terima kasih atas jawabannya, itu baik untuk diketahui, tetapi dalam hal ini ini bukan proc yang disimpan, hanya sebuah file skrip
Andy White
1
@ Gordon Tidak selalu (di sini saya mencari). Lihat jawaban lain (GO trip it up, untuk satu hal)
Mark Sowul
6

Saya menyarankan agar Anda membungkus blok kode yang sesuai dalam blok coba tangkap. Anda kemudian dapat menggunakan acara Raiserror dengan tingkat keparahan 11 untuk memutuskan untuk menangkap blok jika Anda inginkan. Jika Anda hanya ingin meningkatkan kesalahan tetapi melanjutkan eksekusi dalam blok coba kemudian gunakan tingkat keparahan yang lebih rendah.

Masuk akal?

Cheers, John

[Diedit untuk memasukkan Referensi BOL]

http://msdn.microsoft.com/en-us/library/ms175976(SQL.90).aspx

John Sansom
sumber
Saya belum pernah melihat try-catch dalam SQL - maukah Anda memposting contoh singkat tentang apa yang Anda maksud?
Andy White
2
ini baru di tahun 2005. BEGIN TRY {sql_statement | statement_block} END TRY BEGIN CATCH {sql_statement | statement_block} END CATCH [; ]
Sam
@Andy: Referensi ditambahkan, contohnya termasuk.
John Sansom
2
Blok TRY-CATCH tidak memungkinkan GO di dalamnya.
AntonK
4

Anda dapat menggunakan RAISERROR .

Mladen Prajdic
sumber
3
Ini tidak masuk akal untuk memunculkan kesalahan yang dapat dihindari (dengan asumsi kita sedang berbicara tentang validasi referensial di sini) adalah cara yang mengerikan untuk melakukan ini jika validasi dimungkinkan sebelum memasukkan.
Dave Swersky
2
raiserror dapat digunakan sebagai pesan informasi dengan pengaturan tingkat keparahan yang rendah.
Mladen Prajdic
2
Script akan berlanjut kecuali kondisi tertentu yang dinyatakan dalam jawaban yang diterima dipenuhi.
Eric J.
4

Tak satu pun dari ini bekerja dengan pernyataan 'GO'. Dalam kode ini, terlepas dari apakah tingkat keparahannya 10 atau 11, Anda mendapatkan pernyataan PRINT akhir.

Skrip Tes:

-- =================================
PRINT 'Start Test 1 - RAISERROR'

IF 1 = 1 BEGIN
    RAISERROR('Error 1, level 11', 11, 1)
    RETURN
END

IF 1 = 1 BEGIN
    RAISERROR('Error 2, level 11', 11, 1)
    RETURN
END
GO

PRINT 'Test 1 - After GO'
GO

-- =================================
PRINT 'Start Test 2 - Try/Catch'

BEGIN TRY
    SELECT (1 / 0) AS CauseError
END TRY
BEGIN CATCH
    SELECT ERROR_MESSAGE() AS ErrorMessage
    RAISERROR('Error in TRY, level 11', 11, 1)
    RETURN
END CATCH
GO

PRINT 'Test 2 - After GO'
GO

Hasil:

Start Test 1 - RAISERROR
Msg 50000, Level 11, State 1, Line 5
Error 1, level 11
Test 1 - After GO
Start Test 2 - Try/Catch
 CauseError
-----------

ErrorMessage
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Divide by zero error encountered.

Msg 50000, Level 11, State 1, Line 10
Error in TRY, level 11
Test 2 - After GO

Satu-satunya cara untuk membuat karya ini adalah menulis skrip tanpa GOpernyataan. Terkadang itu mudah. Terkadang cukup sulit. (Gunakan sesuatu seperti IF @error <> 0 BEGIN ....)

Rob Garrison
sumber
Tidak dapat melakukannya dengan PROSEDUR BUAT dll. Lihat jawaban saya untuk solusi.
Blorgbeard keluar
Solusi Blogbeard sangat bagus. Saya telah bekerja dengan SQL Server selama bertahun-tahun dan ini adalah pertama kalinya saya melihat ini.
Rob Garrison
4

saya menggunakan RETURN sini sepanjang waktu, bekerja dalam skrip atauStored Procedure

Pastikan Anda ROLLBACKmelakukan transaksi jika berada dalam satu transaksi, jika tidak RETURNsegera akan menghasilkan transaksi terbuka tanpa komitmen

jerryhung
sumber
5
Tidak berfungsi dengan skrip yang berisi banyak batch (pernyataan GO) - lihat jawaban saya untuk cara melakukannya.
Blorgbeard keluar
1
RETURN baru saja keluar dari blok pernyataan saat ini. Jika Anda berada di blok IF END, eksekusi akan dilanjutkan setelah END. Ini berarti Anda tidak dapat menggunakan RETURN untuk mengakhiri eksekusi setelah pengujian untuk beberapa kondisi, karena Anda akan selalu berada di blok JIKA AKHIR.
Gordon
3

Ini solusi saya:

...

BEGIN
    raiserror('Invalid database', 15, 10)
    rollback transaction
    return
END
Casper Leon Nielsen
sumber
3

Anda dapat menggunakan pernyataan GOTO. Coba ini. Ini digunakan penuh untuk Anda.

WHILE(@N <= @Count)
BEGIN
    GOTO FinalStateMent;
END

FinalStatement:
     Select @CoumnName from TableName
Vishal Kiri
sumber
GOTO seharusnya menjadi praktik pengkodean yang buruk, disarankan menggunakan "TRY..CATCH", seperti yang diperkenalkan sejak SQL Server 2008, diikuti oleh THROW pada 2012.
Eddie Kumar
1

Terima kasih untuk jawabannya!

raiserror()berfungsi dengan baik tetapi Anda tidak harus melupakan returnpernyataan itu jika skrip berlanjut tanpa kesalahan! (Hense raiserror bukanlah "throwerror" ;-)) dan tentu saja melakukan rollback jika perlu!

raiserror() Adalah baik untuk memberi tahu orang yang mengeksekusi skrip bahwa ada kesalahan.


sumber
1

Jika Anda hanya menjalankan skrip di Management Studio, dan ingin menghentikan transaksi eksekusi atau rollback (jika digunakan) pada kesalahan pertama, maka cara terbaik yang saya rasa adalah dengan menggunakan blok coba tangkap (SQL 2005 dan seterusnya). Ini bekerja dengan baik di studio Manajemen jika Anda mengeksekusi file skrip. Stored proc selalu dapat menggunakan ini juga.

Bhargav Shah
sumber
1
Apa yang ditambahkan jawaban Anda ke jawaban yang diterima dengan 60+ upvotes? Sudahkah Anda membacanya? Lihat pertanyaan metaSO ini dan Jon Skeet: Coding Blog tentang cara memberikan jawaban yang benar.
Yaroslav
0

Kembali pada hari kami menggunakan yang berikut ini ... berfungsi paling baik:

RAISERROR ('Error! Connection dead', 20, 127) WITH LOG
Lee
sumber
0

Lekatkan dalam blok coba tangkap, maka eksekusi akan ditransfer ke tangkapan.

BEGIN TRY
    PRINT 'This will be printed'
    RAISERROR ('Custom Exception', 16, 1);
    PRINT 'This will not be printed'
END TRY
BEGIN CATCH
    PRINT 'This will be printed 2nd'
END CATCH;
Vasudev
sumber