SQL Server - Stopp oder Unterbrechung der Ausführung eines SQL-Skripts


325

Gibt es eine Möglichkeit, die Ausführung eines SQL-Skripts in SQL Server sofort zu stoppen, z. B. einen Befehl "break" oder "exit"?

Ich habe ein Skript, das einige Überprüfungen und Suchvorgänge durchführt, bevor es mit dem Einfügen beginnt, und ich möchte, dass es beendet wird, wenn eine der Überprüfungen oder Suchvorgänge fehlschlägt.

Antworten:


369

Die Raiserror- Methode

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

Dadurch wird die Verbindung beendet und der Rest des Skripts wird nicht mehr ausgeführt.

Beachten Sie, dass sowohl der Schweregrad 20 oder höher als auch die WITH LOGOption erforderlich sind, damit dies funktioniert.

Dies funktioniert sogar mit GO-Anweisungen, z.

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

Wird Ihnen die Ausgabe geben:

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.

Beachten Sie, dass 'ho' nicht gedruckt wird.

CAVEATS:

  • Dies funktioniert nur, wenn Sie als Administrator ('sysadmin'-Rolle) angemeldet sind und Sie keine Datenbankverbindung haben.
  • Wenn Sie NICHT als Administrator angemeldet sind, schlägt der Aufruf von RAISEERROR () selbst fehl und das Skript wird weiter ausgeführt .
  • Beim Aufrufen mit sqlcmd.exe wird der Exit-Code 2745 gemeldet.

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

Die noexec-Methode

Eine andere Methode, die mit GO-Anweisungen funktioniert, ist set noexec on. Dadurch wird der Rest des Skripts übersprungen. Die Verbindung wird nicht beendet, Sie müssen noexecsie jedoch erneut ausschalten, bevor Befehle ausgeführt werden.

Beispiel:

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.

14
Das ist großartig! Es ist ein bisschen ein "Big Stick" -Ansatz, aber es gibt Zeiten, in denen man ihn wirklich braucht. Beachten Sie, dass sowohl der Schweregrad 20 (oder höher) als auch "WITH LOG" erforderlich sind.
Rob Garrison

5
Beachten Sie, dass mit der noexec-Methode der Rest des Skripts weiterhin interpretiert wird, sodass weiterhin Fehler bei der Kompilierung auftreten, z. B. dass keine Spalte vorhanden ist. Wenn Sie mit bekannten Schemaänderungen, bei denen fehlende Spalten fehlen, bedingt umgehen möchten, indem Sie einen Code überspringen, kann ich nur Folgendes verwenden: r im SQL-Befehlsmodus, um auf externe Dateien zu verweisen.
David Eison

20
Das Noexec-Ding ist großartig. Vielen Dank!
Gaspa79

2
"Dies wird die Verbindung beenden" - es scheint, dass dies nicht der Fall ist, zumindest sehe ich das so.
JCollum

6
Ich habe diese Methode ausprobiert und nicht das richtige Ergebnis erzielt, als mir klar wurde ... Es gibt nur ein E in Raiserror ...
Bobkingof12vs

187

Verwenden Sie einfach eine RETURN (diese funktioniert sowohl innerhalb als auch außerhalb einer gespeicherten Prozedur).


2
Aus irgendeinem Grund dachte ich, dass die Rückgabe in Skripten nicht funktioniert, aber ich habe es einfach versucht, und das tut es! Vielen Dank
Andy White

4
In einem Skript können Sie keine RETURN mit einem Wert wie in einer gespeicherten Prozedur ausführen, aber Sie können eine RETURN ausführen.
Rob Garrison

53
Nein, es endet nur bis zum nächsten GO. Die nächste Charge (nach GO) wird wie gewohnt ausgeführt
Mortb

2
gefährlich anzunehmen, da es nach dem nächsten GO weitergeht.
Justin

1
GO ist ein Skriptterminator oder -begrenzer. Es ist kein SQL-Code. GO ist nur eine Anweisung an den Client, mit dem Sie dem Datenbankmodul den Befehl senden, dass nach dem GO-Trennzeichen ein neues Skript gestartet wird.
Umgekehrter Ingenieur

50

Wenn Sie den SQLCMD-Modus verwenden können, dann die Beschwörung

:on error exit

(EINSCHLIESSLICH des Doppelpunkts) bewirkt, dass RAISERROR das Skript tatsächlich stoppt. Z.B,

: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'

wird ausgegeben:

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

und die Charge wird gestoppt. Wenn der SQLCMD-Modus nicht aktiviert ist, wird ein Analysefehler für den Doppelpunkt angezeigt. Leider ist es nicht vollständig kugelsicher, als ob das Skript ausgeführt wird, ohne sich im SQLCMD-Modus zu befinden. SQL Managment Studio ist selbst nach Analysezeitfehlern ein Kinderspiel! Wenn Sie sie jedoch über die Befehlszeile ausführen, ist dies in Ordnung.


4
Toller Kommentar, danke. Ich werde hinzufügen, dass im SSMS SQLCmd-Modus unter dem Menü Abfrage umgeschaltet wird.
David Peters

Dies ist nützlich - bedeutet, dass Sie die Option -b beim Ausführen nicht benötigen
JonnyRaa

2
dann die Beschwörung ... aber wie kann ich Magic Missle wirken?!
JJS

1
perfekt. erfordert keine zusätzlichen Benutzerrechte für
sysadmin

21

Ich würde RAISERROR nicht verwenden. SQL verfügt über IF-Anweisungen, die für diesen Zweck verwendet werden können. Führen Sie Ihre Validierung und Suche durch und legen Sie lokale Variablen fest. Verwenden Sie dann den Wert der Variablen in IF-Anweisungen, um die Einfügungen bedingt zu machen.

Sie müssten nicht bei jedem Validierungstest ein variables Ergebnis überprüfen. Normalerweise können Sie dies mit nur einer Flag-Variablen tun, um alle übergebenen Bedingungen zu bestätigen:

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

Selbst wenn Ihre Validierung komplexer ist, sollten Sie nur wenige Flag-Variablen benötigen, um sie in Ihre endgültige (n) Prüfung (en) aufzunehmen.


Ja, ich verwende IFs in anderen Teilen des Skripts, aber ich möchte nicht jede lokale Variable überprüfen müssen, bevor ich versuche, eine Einfügung durchzuführen. Ich möchte lieber das gesamte Skript stoppen lassen und den Benutzer zwingen, die Eingaben zu überprüfen. (Dies ist nur ein schnelles und schmutziges Skript)
Andy White

4
Ich bin mir nicht ganz sicher, warum diese Antwort notiert wurde, weil sie technisch korrekt ist, nur nicht, was das Poster "will".
John Sansom

Ist es möglich, mehrere Blöcke in Begin..End zu haben? Bedeutung STATEMENT; GEHEN; AUSSAGE; GEHEN; etc etc? Ich bekomme Fehler und ich denke, das könnte der Grund sein.
Nenotlep

3
Dies ist weitaus zuverlässiger als RAISERROR, insbesondere wenn Sie nicht wissen, wer die Skripte mit welchen Berechtigungen ausführen wird.
Cypher

@ John Sansom: Das einzige Problem, das ich hier sehe, ist, dass die IF-Anweisung nicht funktioniert, wenn Sie versuchen, über eine GO-Anweisung zu verzweigen. Dies ist ein großes Problem, wenn Ihre Skripte auf den GO-Anweisungen (z. B. DDL-Anweisungen) basieren. Hier ist ein Beispiel, das ohne die erste Go-Anweisung funktioniert :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

In SQL 2012+ können Sie THROW verwenden .

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

Von MSDN:

Löst eine Ausnahme aus und überträgt die Ausführung an einen CATCH-Block eines TRY… CATCH-Konstrukts. Wenn ein TRY… CATCH-Konstrukt nicht verfügbar ist, wird die Sitzung beendet. Die Zeilennummer und die Prozedur, bei der die Ausnahme ausgelöst wird, werden festgelegt. Der Schweregrad ist auf 16 eingestellt.


1
THROW soll RAISERROR ersetzen, aber Sie können nachfolgende Stapel in derselben Skriptdatei nicht verhindern.
NReilingh

Korrigieren Sie @NReilingh. Hier ist Blorgbeards Antwort wirklich die einzige Lösung. Es erfordert jedoch Sysadmin (Schweregrad 20), und es ist ziemlich umständlich, wenn das Skript nicht mehrere Stapel enthält.
Jordan Parker

2
Setzen Sie xact abort auf, wenn Sie auch die aktuelle Transcation abbrechen möchten.
Nurettin

13

Ich habe die Ein / Aus-Lösung von noexec erfolgreich um eine Transaktion erweitert, um das Skript vollständig oder gar nicht auszuführen.

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

Anscheinend "versteht" der Compiler die Variable @finished in der IF, auch wenn ein Fehler aufgetreten ist und die Ausführung deaktiviert wurde. Der Wert wird jedoch nur dann auf 1 gesetzt, wenn die Ausführung nicht deaktiviert wurde. Daher kann ich die Transaktion entsprechend gut festschreiben oder zurücksetzen.


Ich verstehe nicht. Ich folgte den Anweisungen. Ich habe nach jedem GO die folgende SQL eingegeben. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Aber die Ausführung wurde nie gestoppt und ich bekam drei Fehler bei "Rolling Back Transaction". Irgendwelche Ideen?
user1161391

12

Sie können Ihre SQL-Anweisung in eine WHILE-Schleife einschließen und bei Bedarf BREAK verwenden

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

5
Ich mag das Aussehen, es scheint ein bisschen schöner zu sein, als Fehler zu verursachen. Ich möchte auf keinen Fall die Pause am Ende vergessen!
Andy White

1
Sie können auch eine Variable verwenden und diese sofort am oberen Rand der Schleife festlegen, um das "Teilen" zu vermeiden. DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; ENDAusführlicher, aber zum Teufel, es ist sowieso

So führen manche Leute goto durch, aber es ist verwirrender zu folgen als goto.
Nurettin

Dieser Ansatz schützt vor einem unerwarteten gelegentlichen GO. Dankbar.
it3xl

10

Sie können den Ausführungsfluss mithilfe von GOTO- Anweisungen ändern :

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

/* our code */

EndScript:

2
Die Verwendung von goto ist ein akzeptabler Weg, um Ausnahmen zu behandeln. Reduziert die Anzahl der Variablen und die Verschachtelung und verursacht keine Unterbrechung. Es ist wahrscheinlich der archaischen Ausnahmebehandlung vorzuziehen, die SQL Server-Skripte zulässt.
Antonio Drusin

Genau wie ALLE anderen Vorschläge hier funktioniert dies nicht, wenn "unser Code" eine "GO" -Anweisung enthält.
Mike Gledhill

9

Bei der weiteren Verfeinerung der Sglasses-Methode erzwingen die obigen Zeilen die Verwendung des SQLCMD-Modus und zittern entweder den Scrirpt, wenn der SQLCMD-Modus nicht verwendet wird, oder verwenden ihn :on error exit, um einen Fehler zu
beenden. CONTEXT_INFO wird verwendet, um den Status zu verfolgen.

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-------------

2
Dies ist die einzige Möglichkeit, die SSMS-Wahnsinnigkeit zu umgehen, das Skript nicht abbrechen zu können. Aber ich habe zu Beginn 'SET NOEXEC OFF' und 'SET NOEXEC ON' hinzugefügt, wenn ich mich nicht im SQLCMD-Modus befinde. Andernfalls wird das eigentliche Skript fortgesetzt, es sei denn, Sie geben einen Fehler auf Stufe 20 mit log aus.
Mark Sowul

8

Ist das eine gespeicherte Prozedur? Wenn ja, könnten Sie einfach eine Rückgabe durchführen, z. B. "Return NULL".


Vielen Dank für die Antwort, das ist gut zu wissen, aber in diesem Fall ist es kein gespeicherter Prozess, sondern nur eine Skriptdatei
Andy White

1
@ Gordon Nicht immer (hier suche ich). Siehe andere Antworten (GO
stolpert

6

Ich würde vorschlagen, dass Sie Ihren entsprechenden Codeblock in einen try catch-Block einschließen. Sie können dann das Raiserror-Ereignis mit einem Schweregrad von 11 verwenden, um auf Wunsch zum catch-Block zu gelangen. Wenn Sie nur Fehler auslösen möchten, aber die Ausführung innerhalb des try-Blocks fortsetzen möchten, verwenden Sie einen niedrigeren Schweregrad.

Sinn ergeben?

Prost, John

[Bearbeitet, um BOL-Referenz einzuschließen]

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


Ich habe noch nie einen Try-Catch in SQL gesehen. Würde es Ihnen etwas ausmachen, ein kurzes Beispiel dafür zu veröffentlichen, was Sie meinen?
Andy White

2
Es ist neu für 2005. BEGIN TRY {sql_statement | Anweisungsblock} END TRY BEGIN CATCH {sql_statement | Anweisungsblock} END CATCH [; ]
Sam

@Andy: Referenz hinzugefügt, Beispiel enthalten.
John Sansom

2
Der TRY-CATCH-Block erlaubt kein GO in sich.
AntonK

4

Sie können RAISERROR verwenden .


3
Dies macht keinen Sinn, um einen vermeidbaren Fehler auszulösen (vorausgesetzt, es handelt sich hier um eine referenzielle Validierung). Dies ist eine schreckliche Möglichkeit, wenn eine Validierung möglich ist, bevor die Einfügungen stattfinden.
Dave Swersky

2
raiserror kann als Informationsnachricht mit einem niedrigen Schweregrad verwendet werden.
Mladen Prajdic

2
Das Skript wird fortgesetzt, sofern bestimmte in der akzeptierten Antwort angegebene Bedingungen nicht erfüllt sind.
Eric J.

4

Keine dieser Anweisungen funktioniert mit 'GO'-Anweisungen. In diesem Code erhalten Sie unabhängig davon, ob der Schweregrad 10 oder 11 beträgt, die endgültige PRINT-Anweisung.

Testskript:

-- =================================
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

Ergebnisse:

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

Die einzige Möglichkeit, dies zu erreichen, besteht darin, das Skript ohne GOAnweisungen zu schreiben . Manchmal ist das einfach. Manchmal ist es ziemlich schwierig. (Verwenden Sie so etwas wie IF @error <> 0 BEGIN ....)


Mit CREATE PROCEDURE usw. geht das nicht. Eine Antwort finden Sie in meiner Antwort.
Blorgbeard ist am

Die Lösung von Blogbeard ist großartig. Ich arbeite seit Jahren mit SQL Server und dies ist das erste Mal, dass ich dies sehe.
Rob Garrison

4

Ich benutze RETURNhier die ganze Zeit, arbeitet in Skript oderStored Procedure

Stellen Sie sicher, dass Sie ROLLBACKdie Transaktion haben, wenn Sie sich in einer befinden. Andernfalls wird RETURNsofort eine offene, nicht festgeschriebene Transaktion angezeigt


5
Funktioniert nicht mit einem Skript, das mehrere Stapel enthält (GO-Anweisungen) - siehe meine Antwort dazu.
Blorgbeard ist am

1
RETURN beendet nur den aktuellen Anweisungsblock. Wenn Sie sich in einem IF END-Block befinden, wird die Ausführung nach dem END fortgesetzt. Dies bedeutet, dass Sie RETURN nicht verwenden können, um die Ausführung nach dem Testen auf eine bestimmte Bedingung zu beenden, da Sie sich immer im IF END-Block befinden.
cdonner

3

Das war meine Lösung:

...

BEGIN
    raiserror('Invalid database', 15, 10)
    rollback transaction
    return
END

3

Sie können die GOTO-Anweisung verwenden. Versuche dies. Dies ist voll für Sie zu nutzen.

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

FinalStatement:
     Select @CoumnName from TableName

GOTO soll eine schlechte Codierungspraxis sein. Die Verwendung von "TRY..CATCH" wird empfohlen, da es seit SQL Server 2008 eingeführt wurde, gefolgt von THROW im Jahr 2012.
Eddie Kumar

1

Danke für die Antwort!

raiserror()funktioniert gut, aber Sie sollten die returnAnweisung nicht vergessen , sonst wird das Skript ohne Fehler fortgesetzt! (hense der Raiserror ist kein "Throwerror" ;-)) und natürlich bei Bedarf einen Rollback machen!

raiserror() Es ist schön, der Person, die das Skript ausführt, mitzuteilen, dass ein Fehler aufgetreten ist.


1

Wenn Sie einfach ein Skript in Management Studio ausführen und die Ausführung oder Rollback-Transaktion (falls verwendet) beim ersten Fehler stoppen möchten, ist es meiner Meinung nach am besten, den try catch-Block zu verwenden (ab SQL 2005). Dies funktioniert gut in Management Studio, wenn Sie eine Skriptdatei ausführen. Gespeicherte Prozesse können dies auch immer verwenden.


1
Was trägt Ihre Antwort zur akzeptierten Antwort mit mehr als 60 Upvotes bei? Hast du es gelesen? Überprüfen Sie diese metaSO-Frage und Jon Skeet: Coding Blog, wie Sie eine richtige Antwort geben.
Jaroslaw

0

Früher haben wir Folgendes verwendet ... hat am besten funktioniert:

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

0

Schließen Sie es in einen try catch-Block ein, dann wird die Ausführung an catch übertragen.

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;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.