SQL Server - arrêter ou interrompre l'exécution d'un script SQL

325

Existe-t-il un moyen d'arrêter immédiatement l'exécution d'un script SQL dans le serveur SQL, comme une commande "break" ou "exit"?

J'ai un script qui effectue des validations et des recherches avant qu'il ne commence à faire des insertions, et je veux qu'il s'arrête si l'une des validations ou des recherches échoue.

Andy White
la source

Réponses:

371

La méthode raiserror

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

Cela mettra fin à la connexion, arrêtant ainsi le reste du script de s'exécuter.

Notez que le niveau de gravité 20 ou supérieur et l' WITH LOGoption sont nécessaires pour que cela fonctionne de cette façon.

Cela fonctionne même avec les instructions GO, par exemple.

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

Vous donnera la sortie:

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.

Notez que «ho» n'est pas imprimé.

CAVEATS:

  • Cela ne fonctionne que si vous êtes connecté en tant qu'administrateur (rôle «sysadmin») et vous laisse également sans connexion à la base de données.
  • Si vous n'êtes PAS connecté en tant qu'administrateur, l'appel RAISEERROR () lui-même échouera et le script continuera de s'exécuter .
  • Lorsqu'il est appelé avec sqlcmd.exe, le code de sortie 2745 sera signalé.

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

La méthode noexec

Une autre méthode qui fonctionne avec les instructions GO est set noexec on. Cela provoque le reste du script à ignorer. Il ne met pas fin à la connexion, mais vous devez la noexecdésactiver à nouveau avant l'exécution de toute commande.

Exemple:

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 est sorti
la source
14
C'est génial! C'est un peu une approche "big stick", mais il y a des moments où vous en avez vraiment besoin. Notez qu'il requiert à la fois la gravité 20 (ou supérieure) et "AVEC JOURNAL".
Rob Garrison
5
Notez qu'avec la méthode noexec, le reste du script est toujours interprété, vous obtiendrez donc toujours des erreurs de compilation, telles que la colonne n'existe pas. Si vous souhaitez traiter de manière conditionnelle les modifications de schéma connues impliquant des colonnes manquantes en ignorant du code, la seule façon que je sache de le faire est d'utiliser: r en mode sqlcommand pour référencer des fichiers externes.
David Eison
20
La chose noexec est géniale. Merci beaucoup!
Gaspa79
2
"Cela mettra fin à la connexion" - il semble que non, du moins c'est ce que je vois.
jcollum
6
J'essayais cette méthode et n'obtenais pas le bon résultat quand je me suis rendu compte ... Il n'y a qu'un seul E dans raiserror ...
bobkingof12vs
187

Utilisez simplement un RETOUR (cela fonctionnera à l'intérieur et à l'extérieur d'une procédure stockée).

Gordon Bell
la source
2
Pour une raison quelconque, je pensais que le retour ne fonctionnait pas dans les scripts, mais je l'ai juste essayé, et ça marche! Merci
Andy White
4
Dans un script, vous ne pouvez pas effectuer un RETOUR avec une valeur comme vous le pouvez dans une procédure stockée, mais vous pouvez effectuer un RETOUR.
Rob Garrison
53
Non, il ne se termine que jusqu'au prochain GO Le prochain lot (après GO) se déroulera comme d'habitude
mortb
2
dangereux à assumer car il continuera après le prochain GO.
Justin
1
GO est un terminateur ou délimiteur de script; ce n'est pas du code SQL. GO n'est qu'une instruction au client que vous utilisez pour envoyer une commande au moteur de base de données qu'un nouveau script démarre après le délimiteur GO.
Ingénieur inversé
50

Si vous pouvez utiliser le mode SQLCMD, l'incantation

:on error exit

(Y COMPRIS les deux points) entraînera RAISERROR réellement arrêter le script. Par exemple,

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

affichera:

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

et le lot s'arrêtera. Si le mode SQLCMD n'est pas activé, vous obtiendrez une erreur d'analyse sur les deux points. Malheureusement, ce n'est pas complètement à l'épreuve des balles comme si le script était exécuté sans être en mode SQLCMD, SQL Managment Studio passe au-delà même des erreurs de temps d'analyse! Pourtant, si vous les exécutez à partir de la ligne de commande, c'est très bien.

Lunettes
la source
4
Grand commentaire, merci. J'ajouterai qu'en mode SSMS SQLCmd, on bascule sous le menu Requête.
David Peters
ceci est utile - signifie que vous n'avez pas besoin de l'option -b lors de l'exécution
JonnyRaa
2
puis l'incantation ... mais comment puis-je lancer Magic Missle?!
JJS
1
parfait. ne nécessite pas de droits d'utilisateur supplémentaires pour sysadmin ultra
Pac0
21

Je n'utiliserais pas RAISERROR - SQL a des instructions IF qui peuvent être utilisées à cette fin. Effectuez votre validation et vos recherches et définissez des variables locales, puis utilisez la valeur des variables dans les instructions IF pour rendre les insertions conditionnelles.

Vous n'auriez pas besoin de vérifier un résultat variable de chaque test de validation. Vous pouvez généralement le faire avec une seule variable indicateur pour confirmer toutes les conditions remplies:

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

Même si votre validation est plus complexe, vous ne devriez avoir besoin que de quelques variables d'indicateur à inclure dans vos vérifications finales.

Dave Swersky
la source
Oui, j'utilise des IF dans d'autres parties du script, mais je ne veux pas avoir à vérifier chaque variable locale avant d'essayer de faire un insert. Je préfère simplement que tout le script s'arrête et forcer l'utilisateur à vérifier les entrées. (Ce n'est qu'un script rapide et sale)
Andy White
4
Je ne sais pas trop pourquoi cette réponse a été notée parce qu'elle est techniquement correcte, mais pas ce que l'affiche "veut" faire.
John Sansom
Est-il possible d'avoir plusieurs blocs dans Begin..End? Signification DÉCLARATION; ALLER; DÉCLARATION; ALLER; etc? Je reçois des erreurs et je suppose que cela pourrait être la raison.
Nenotlep
3
C'est beaucoup plus fiable que RAISERROR, surtout si vous ne savez pas qui va exécuter les scripts et avec quels privilèges.
Cypher
@John Sansom: Le seul problème que je vois ici est que l'instruction IF ne fonctionne pas si vous essayez de créer une branche sur une instruction GO. C'est un gros problème si vos scripts s'appuient sur les instructions GO (par exemple les instructions DDL). Voici un exemple qui fonctionne sans la première déclaration: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

Dans SQL 2012+, vous pouvez utiliser THROW .

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

Depuis MSDN:

Déclenche une exception et transfère l'exécution à un bloc CATCH d'une construction TRY… CATCH ... Si une construction TRY… CATCH n'est pas disponible, la session est terminée. Le numéro de ligne et la procédure où l'exception est déclenchée sont définis. La gravité est définie sur 16.

Jordan Parker
la source
1
THROW est destiné à remplacer RAISERROR, mais vous ne pouvez pas empêcher les lots suivants dans le même fichier de script avec lui.
NReilingh
Corrigez @NReilingh. C'est là que la réponse de Blorgbeard est vraiment la seule solution. Il nécessite cependant sysadmin (niveau de gravité 20), et il est assez lourd s'il n'y a pas plusieurs lots dans le script.
Jordan Parker
2
activez xact abort si vous souhaitez également annuler la transcation en cours.
nurettin
13

J'ai étendu la solution marche / arrêt noexec avec succès avec une transaction pour exécuter le script de manière tout ou rien.

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

Apparemment, le compilateur "comprend" la variable @finished dans l'IF, même s'il y a eu une erreur et que l'exécution a été désactivée. Cependant, la valeur n'est définie sur 1 que si l'exécution n'a pas été désactivée. Par conséquent, je peux bien valider ou annuler la transaction en conséquence.

Tz_
la source
Je ne comprends pas. J'ai suivi les instructions. J'ai entré le SQL suivant après chaque GO. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Mais l'exécution n'a jamais cessé, et j'ai fini avec trois erreurs de "Rolling back Transaction". Des idées?
user1161391
12

vous pouvez envelopper votre instruction SQL dans une boucle WHILE et utiliser BREAK si nécessaire

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
la source
5
J'aime bien l'apparence de cela, cela semble un peu plus agréable que de soulever une erreur. Je ne veux certainement pas oublier la pause à la fin!
Andy White
1
Vous pouvez également utiliser une variable et la placer immédiatement en haut de la boucle pour éviter le "split". DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; ENDPlus verbeux, mais bon, c'est TSQL quand même ;-)
C'est ainsi que certaines personnes exécutent goto, mais il est plus déroutant de suivre que goto.
nurettin
Cette approche protège d'un GO occasionnel inattendu. Reconnaissant.
it3xl
10

Vous pouvez modifier le flux d'exécution à l'aide des instructions GOTO :

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

/* our code */

EndScript:
Charlie
la source
2
l'utilisation de goto est une façon acceptable de gérer les exceptions. Réduit la quantité de variables et d'imbrication et ne provoque pas de déconnexion. Il est probablement préférable à la gestion des exceptions archaïques permise par les scripts SQL Server.
Antonio Drusin
Tout comme TOUTES les autres suggestions ici, cela ne fonctionne pas si "notre code" contient une instruction "GO".
Mike Gledhill
9

Pour affiner la méthode Sglasses, les lignes ci-dessus forcent l'utilisation du mode SQLCMD et tréminent le scirpt si elles n'utilisent pas le mode SQLCMD ou utilisent :on error exitpour quitter en cas d'erreur
CONTEXT_INFO est utilisé pour garder une trace de l'état.

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
la source
2
C'est le seul moyen que j'ai trouvé pour contourner la folie SSMS de ne pas pouvoir abandonner le script. Mais j'ai ajouté 'SET NOEXEC OFF' au début et 'SET NOEXEC ON' s'il n'est pas en mode SQLCMD, sinon le script réel continuera à moins que vous ne signaliez une erreur au niveau 20 avec log.
Mark Sowul
8

S'agit-il d'une procédure stockée? Si c'est le cas, je pense que vous pouvez simplement faire un retour, comme "Return NULL";

mtazva
la source
Merci pour la réponse, c'est bon à savoir, mais dans ce cas ce n'est pas un proc stocké, juste un fichier script
Andy White
1
@Gordon Pas toujours (ici je cherche). Voir les autres réponses (GO le déclenche, pour une chose)
Mark Sowul
6

Je suggère que vous enveloppiez votre bloc de code approprié dans un bloc try catch. Vous pouvez ensuite utiliser l'événement Raiserror avec une gravité de 11 afin de passer au bloc catch si vous le souhaitez. Si vous souhaitez simplement augmenter les erreurs mais continuer l'exécution dans le bloc try, utilisez une gravité inférieure.

Ça a du sens?

À la vôtre, John

[Modifié pour inclure la référence BOL]

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

John Sansom
la source
Je n'ai jamais vu d'essayer-attraper dans SQL - cela vous dérangerait de poster un exemple rapide de ce que vous voulez dire?
Andy White
2
c'est nouveau en 2005. DÉBUT TRY {sql_statement | statement_block} END TRY BEGIN CATCH {sql_statement | instruction_block} END CATCH [; ]
Sam
@Andy: référence ajoutée, exemple inclus.
John Sansom
2
Le bloc TRY-CATCH ne permet pas d'aller à l'intérieur de lui-même.
AntonK
4

vous pouvez utiliser RAISERROR .

Mladen Prajdic
la source
3
Cela n'a aucun sens pour moi de soulever une erreur évitable (en supposant que nous parlons ici de validation référentielle) est une façon horrible de le faire si la validation est possible avant que les insertions aient lieu.
Dave Swersky
2
raiserror peut être utilisé comme un message d'information avec un paramètre de gravité faible.
Mladen Prajdic
2
Le script continuera à moins que certaines conditions énoncées dans la réponse acceptée ne soient remplies.
Eric J.
4

Aucun de ces éléments ne fonctionne avec des instructions «GO». Dans ce code, que la gravité soit de 10 ou 11, vous obtenez l'instruction finale PRINT.

Script de test:

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

Résultats:

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

La seule façon de faire ce travail est d'écrire le script sans GOinstructions. Parfois, c'est facile. Parfois, c'est assez difficile. (Utilisez quelque chose comme IF @error <> 0 BEGIN ....)

Rob Garrison
la source
Je ne peux pas faire ça avec CREATE PROCEDURE etc. Voir ma réponse pour une solution.
Blorgbeard est sorti le
La solution de Blogbeard est excellente. Je travaille avec SQL Server depuis des années et c'est la première fois que je vois cela.
Rob Garrison
4

J'utilise RETURNici tout le temps, travaille en script ouStored Procedure

Assurez-vous que vous ROLLBACKla transaction si vous êtes en un, sinon RETURNimmédiatement entraînera une transaction non validée ouverte

jerryhung
la source
5
Ne fonctionne pas avec un script contenant plusieurs lots (instructions GO) - voir ma réponse pour savoir comment procéder.
Blorgbeard est sorti le
1
RETURN quitte simplement le bloc d'instructions actuel. Si vous êtes dans un bloc IF END, l'exécution continuera après la fin. Cela signifie que vous ne pouvez pas utiliser RETURN pour terminer l'exécution après avoir testé une condition, car vous serez toujours dans le bloc IF END.
cdonner
3

C'était ma solution:

...

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

Vous pouvez utiliser l'instruction GOTO. Essaye ça. C'est une utilisation complète pour vous.

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

FinalStatement:
     Select @CoumnName from TableName
Vishal Kiri
la source
GOTO est censé être une mauvaise pratique de codage, l'utilisation de "TRY..CATCH" est recommandée, car elle a été introduite depuis SQL Server 2008, suivie de THROW en 2012.
Eddie Kumar
1

Merci pour la réponse!

raiserror()fonctionne bien mais vous ne devez pas oublier l' returninstruction sinon le script continue sans erreur! (hense le raiserror n'est pas un "throwerror" ;-)) et bien sûr faire un rollback si nécessaire!

raiserror() est agréable de dire à la personne qui exécute le script que quelque chose s'est mal passé.


la source
1

Si vous exécutez simplement un script dans Management Studio et que vous souhaitez arrêter l'exécution ou la transaction de restauration (si elle est utilisée) à la première erreur, alors la meilleure façon, je pense, est d'utiliser le bloc try catch (à partir de SQL 2005). Cela fonctionne bien dans Management Studio si vous exécutez un fichier de script. Le proc stocké peut toujours l'utiliser également.

Bhargav Shah
la source
1
Qu'est-ce que votre réponse ajoute à la réponse acceptée avec plus de 60 votes positifs? L'avez-vous lu? Consultez cette question metaSO et Jon Skeet: Coding Blog sur la façon de donner une réponse correcte.
Yaroslav
0

À l'époque, nous utilisions les éléments suivants ... qui fonctionnaient le mieux:

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

Placez-le dans un bloc try catch, puis l'exécution sera transférée vers 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;
Vasudev
la source