SQL Server: interrompe o interrompe l'esecuzione di uno script SQL


325

C'è un modo per interrompere immediatamente l'esecuzione di uno script SQL nel server SQL, come un comando "break" o "exit"?

Ho uno script che esegue alcune convalide e ricerche prima che inizi a fare inserimenti e voglio che si interrompa se una delle convalide o delle ricerche fallisce.

Risposte:


371

Il metodo raiserror

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

Ciò interromperà la connessione, interrompendo così l'esecuzione del resto dello script.

Si noti che sia il livello di gravità 20 o superiore e l' WITH LOGopzione sono necessari affinché funzioni in questo modo.

Funziona anche con le dichiarazioni GO, ad es.

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

Ti darà l'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.

Notare che 'ho' non è stampato.

Avvertenze:

  • Funziona solo se si è effettuato l'accesso come admin (ruolo 'sysadmin') e non si ha alcuna connessione al database.
  • Se NON si è effettuato l'accesso come amministratore, la chiamata RAISEERROR () stessa non riuscirà e lo script continuerà l'esecuzione .
  • Quando viene richiamato con sqlcmd.exe, verrà segnalato il codice di uscita 2745.

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

Il metodo noexec

Un altro metodo che funziona con le istruzioni GO è set noexec on. Questo fa sì che il resto dello script venga ignorato. Non interrompe la connessione, ma è necessario noexecdisattivare nuovamente prima di eseguire qualsiasi comando.

Esempio:

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
È fantastico! È un po 'un approccio "big stick", ma ci sono momenti in cui ne hai davvero bisogno. Si noti che richiede sia la gravità 20 (o superiore) che "CON LOG".
Rob Garrison,

5
Si noti che con il metodo noexec il resto dello script viene ancora interpretato, quindi si otterranno comunque errori di compilazione, come la colonna non esiste. Se si desidera gestire in modo condizionale modifiche dello schema note che coinvolgono colonne mancanti ignorando un po 'di codice, l'unico modo che conosco per farlo è utilizzare: r in modalità sqlcommand per fare riferimento a file esterni.
David Eison,

20
La cosa noexec è fantastica. Molte grazie!
Gaspa79,

2
"Questo interromperà la connessione" - sembra che non lo sia, almeno è quello che sto vedendo.
jcollum,

6
Stavo provando questo metodo e non ho ottenuto il risultato giusto quando mi sono reso conto ... C'è solo una E in raiserror ...
bobkingof12vs

187

Basta usare un INVIO (funzionerà sia all'interno che all'esterno di una procedura memorizzata).


2
Per qualche ragione, pensavo che il ritorno non funzionasse negli script, ma l'ho appena provato, e lo fa! Grazie
Andy White,

4
In uno script, non è possibile eseguire un RITORNO con un valore come è possibile in una procedura memorizzata, ma è possibile eseguire un RITORNO.
Rob Garrison,

53
No, termina solo fino al prossimo GO. Il prossimo batch (dopo GO) verrà eseguito normalmente
mort

2
pericoloso da assumere poiché continuerà dopo il prossimo GO.
Giustino,

1
GO è un terminatore di script o delimitatore; non è codice SQL. GO è solo un'istruzione per il client che si sta utilizzando per inviare il comando al motore di database che un nuovo script si avvia dopo il delimitatore GO.
Ingegnere invertito

50

Se puoi usare la modalità SQLCMD, allora l'incantesimo

:on error exit

(COMPRESO i due punti) farà sì che RAISERROR interrompa effettivamente la sceneggiatura. Per esempio,

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

produrrà:

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

e il batch si fermerà. Se la modalità SQLCMD non è attivata, verrà visualizzato un errore di analisi sui due punti. Sfortunatamente, non è completamente a prova di proiettile, come se lo script venisse eseguito senza essere in modalità SQLCMD, SQL Managment Studio fa passare il tempo anche in caso di errori di analisi! Tuttavia, se li stai eseguendo dalla riga di comando, questo va bene.


4
Ottimo commento, grazie. Aggiungerò che in SSMS la modalità SQLCmd è attiva / disattiva nel menu Query.
David Peters,

questo è utile - significa che non è necessaria l'opzione -b quando si esegue
JonnyRaa

2
poi l'incantesimo ... ma come posso lanciare Magic Missle ?!
JJS,

1
Perfetto. non richiede diritti utente extra extra per sysadmin
Pac0

21

Non userei RAISERROR - SQL ha istruzioni IF che possono essere utilizzate per questo scopo. Esegui la convalida e le ricerche e imposta le variabili locali, quindi utilizza il valore delle variabili nelle istruzioni IF per rendere condizionali gli inserimenti.

Non è necessario controllare un risultato variabile di ogni test di convalida. Di solito è possibile farlo con una sola variabile flag per confermare tutte le condizioni passate:

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

Anche se la convalida è più complessa, è necessario includere solo alcune variabili flag da includere nei controlli finali.


Sì, sto usando IF in altre parti dello script, ma non voglio controllare ogni variabile locale prima di provare a fare un inserimento. Preferirei solo interrompere l'intero script e costringere l'utente a controllare gli input. (Questa è solo una sceneggiatura veloce e sporca)
Andy White,

4
Non sono del tutto sicuro del motivo per cui questa risposta è stata contrassegnata in quanto è tecnicamente corretta, ma non è quello che il poster "vuole" fare.
John Sansom,

È possibile avere più blocchi in Begin..End? Significato DICHIARAZIONE; PARTIRE; DICHIARAZIONE; PARTIRE; ecc ecc? Ricevo errori e suppongo che potrebbe essere la ragione.
Nenotlep,

3
Questo è molto più affidabile di RAISERROR, soprattutto se non sai chi eseguirà gli script e con quali privilegi.
Cypher,

@ John Sansom: L'unico problema che vedo qui è che l'istruzione IF non funziona se si sta tentando di eseguire il branching su un'istruzione GO. Questo è un grosso problema se i tuoi script si basano sulle istruzioni GO (ad es. Istruzioni DDL). Ecco un esempio che funziona senza la prima dichiarazione go: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+, è possibile utilizzare THROW .

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

Da MSDN:

Solleva un'eccezione e trasferisce l'esecuzione su un blocco CATCH di un costrutto TRY ... CATCH ... Se un costrutto TRY ... CATCH non è disponibile, la sessione termina. Vengono impostati il ​​numero di riga e la procedura in cui viene sollevata l'eccezione. La gravità è impostata su 16.


1
THROW ha lo scopo di sostituire RAISERROR, ma non è possibile impedire i batch successivi nello stesso file di script con esso.
NReilingh,

@NReilingh corretto. Ecco dove la risposta di Blorgbeard è davvero l'unica soluzione. Tuttavia richiede sysadmin (livello di gravità 20) ed è abbastanza pesante se non ci sono più batch nello script.
Jordan Parker,

2
impostare xact abort se si desidera annullare anche la transazione corrente.
Nurettin,

13

Ho esteso la soluzione on / off noexec con successo con una transazione per eseguire lo script in un modo tutto o niente.

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

Apparentemente il compilatore "capisce" la variabile @finished nell'IF, anche se si è verificato un errore e l'esecuzione è stata disabilitata. Tuttavia, il valore è impostato su 1 solo se l'esecuzione non è stata disabilitata. Quindi posso eseguire correttamente il commit o il rollback della transazione di conseguenza.


Non capisco. Ho seguito le istruzioni. Ho inserito il seguente SQL dopo ogni GO. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Ma l'esecuzione non si è mai fermata, e ho finito con tre errori "Rollback back Transaction". Qualche idea?
user1161391

12

potresti avvolgere la tua istruzione SQL in un ciclo WHILE e usare BREAK se necessario

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
Mi piace un po 'l'aspetto di questo, sembra un po' più bello che aumentare l'errore. Sicuramente non voglio dimenticare la pausa alla fine!
Andy White,

1
È inoltre possibile utilizzare una variabile e impostarla immediatamente nella parte superiore del ciclo per evitare la "divisione". DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; ENDPiù dettagliato, ma diamine, è comunque TSQL ;-)

Questo è il modo in cui alcune persone eseguono goto, ma è più confuso da seguire che goto.
Nurettin,

Questo approccio protegge da un GO occasionale inaspettato. Riconoscente.
it3xl,

10

Puoi modificare il flusso di esecuzione usando le istruzioni GOTO :

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

/* our code */

EndScript:

2
usare goto è un modo accettabile per gestire le eccezioni. Riduce la quantità di variabili e nidificazione e non provoca una disconnessione. Probabilmente è preferibile la gestione arcaica delle eccezioni consentita dagli script di SQL Server.
Antonio Drusin,

Proprio come TUTTI gli altri suggerimenti qui, questo non funziona se "il nostro codice" contiene un'istruzione "GO".
Mike Gledhill il

9

Ulteriormente perfezionando il metodo Sglasses, le righe precedenti forzano l'uso della modalità SQLCMD e treminano lo scirpt se non si utilizza la modalità SQLCMD o si utilizza :on error exitper uscire da qualsiasi errore
CONTEXT_INFO viene utilizzato per tenere traccia dello stato.

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
Questo è l'unico modo in cui ho scoperto di aggirare la follia SSMS di non riuscire a interrompere lo script. Ma all'inizio ho aggiunto "SET NOEXEC OFF" e "SET NOEXEC ON" se non in modalità SQLCMD, altrimenti lo script effettivo continuerà a meno che non si generi un errore al livello 20 con log.
Mark Sowul,

8

È una procedura memorizzata? In tal caso, penso che potresti semplicemente fare un Return, come "Return NULL";


Grazie per la risposta, è buono a sapersi, ma in questo caso non è un proc memorizzato, ma solo un file di script
Andy White

1
@Gordon Non sempre (qui sto cercando). Vedi altre risposte (GO inciampa, per prima cosa)
Mark Sowul,

6

Suggerirei di racchiudere il blocco di codice appropriato in un blocco catch catch. È quindi possibile utilizzare l'evento Raiserror con una gravità di 11 al fine di rompere il blocco catch, se lo si desidera. Se si desidera solo eseguire raiserrors ma continuare l'esecuzione all'interno del blocco try, utilizzare una gravità inferiore.

Ha senso?

Saluti, John

[Modificato per includere il riferimento BOL]

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


Non ho mai visto un tentativo in SQL - ti dispiacerebbe pubblicare un rapido esempio di cosa intendi?
Andy White,

2
è nuovo per il 2005. INIZIA PROVA {sql_statement | statement_block} FINE PROVA INIZIA CATTURA {sql_statement | statement_block} END CATCH [; ]
Sam

@Andy: riferimento aggiunto, esempio incluso.
John Sansom,

2
Il blocco TRY-CATCH non consente GO all'interno di se stesso.
AntonK,

4

puoi usare RAISERROR .


3
Questo non ha senso per me - sollevare un errore evitabile (supponendo che stiamo parlando di validazione referenziale qui) è un modo orribile per farlo se la validazione è possibile prima che avvengano gli inserti.
Dave Swersky,

2
raiserror può essere utilizzato come messaggio informativo con un'impostazione di gravità bassa.
Mladen Prajdic,

2
Lo script continuerà a meno che non siano soddisfatte determinate condizioni indicate nella risposta accettata.
Eric J.

4

Nessuno di questi funziona con le dichiarazioni "GO". In questo codice, indipendentemente dal fatto che la gravità sia 10 o 11, si ottiene l'istruzione PRINT finale.

Test script:

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

risultati:

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

L'unico modo per farlo funzionare è scrivere la sceneggiatura senza GOistruzioni. A volte è facile. A volte è abbastanza difficile. (Usa qualcosa di simile IF @error <> 0 BEGIN ....)


Non posso farlo con CREATE PROCEDURE ecc. Vedi la mia risposta per una soluzione.
Blorgbeard esce il

La soluzione di Blogbeard è fantastica. Lavoro con SQL Server da anni e questa è la prima volta che lo vedo.
Rob Garrison,

4

Uso RETURNqui tutto il tempo, funziona in script oStored Procedure

Assicurati di ROLLBACKeffettuare la transazione se sei in uno, altrimenti RETURNsi tradurrà immediatamente in una transazione aperta senza commit


5
Non funziona con uno script contenente più batch (istruzioni GO) - vedi la mia risposta per sapere come farlo.
Blorgbeard esce il

1
INVIO esce solo dall'attuale blocco di istruzioni. Se ci si trova in un blocco END IF, l'esecuzione continuerà dopo END. Ciò significa che non è possibile utilizzare INVIO per terminare l'esecuzione dopo il test per alcune condizioni, poiché si sarà sempre nel blocco IF END.
Cdonner,

3

Questa era la mia soluzione:

...

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

3

È possibile utilizzare l'istruzione GOTO. Prova questo. Questo è l'uso completo per te.

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

FinalStatement:
     Select @CoumnName from TableName

GOTO dovrebbe essere una cattiva pratica di codifica, si consiglia l'uso di "TRY..CATCH", poiché è stato introdotto da SQL Server 2008, seguito da THROW nel 2012.
Eddie Kumar,

1

Grazie per la risposta!

raiserror()funziona bene ma non dovresti dimenticare l' returnaffermazione altrimenti lo script continua senza errori! (hense il raiserror non è un "throwerror" ;-)) e ovviamente facendo un rollback se necessario!

raiserror() è bello dire alla persona che esegue la sceneggiatura che qualcosa è andato storto.


1

Se si sta semplicemente eseguendo uno script in Management Studio e si desidera interrompere l'esecuzione o il rollback della transazione (se utilizzata) al primo errore, il modo migliore per calcolare è utilizzare provare a prendere il blocco (da SQL 2005 in poi). Funziona bene in Management Studio se si esegue un file di script. Anche il proc memorizzato può sempre usare questo.


1
Cosa aggiunge la tua risposta alla risposta accettata con oltre 60 voti? Lo hai letto? Controlla questa domanda metaSO e Jon Skeet: Blog di programmazione su come dare una risposta corretta.
Yaroslav,

0

In passato abbiamo usato il seguente ... ha funzionato meglio:

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

0

Racchiudilo in un blocco catch di prova, quindi l'esecuzione verrà trasferita in modalità catch.

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.