SQL Server: detener o interrumpir la ejecución de un script SQL

325

¿Hay alguna manera de detener inmediatamente la ejecución de un script SQL en el servidor SQL, como un comando "break" o "exit"?

Tengo un script que realiza algunas validaciones y búsquedas antes de que comience a insertar, y quiero que se detenga si falla alguna de las validaciones o búsquedas.

Andy White
fuente

Respuestas:

371

El método raiserror

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

Esto terminará la conexión, lo que detendrá la ejecución del resto del script.

Tenga en cuenta que tanto el nivel de gravedad 20 o superior como la WITH LOGopción son necesarios para que funcione de esta manera.

Esto incluso funciona con declaraciones GO, por ejemplo.

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

Te dará el resultado:

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.

Tenga en cuenta que 'ho' no se imprime.

CUEVAS

  • Esto solo funciona si ha iniciado sesión como administrador (rol 'sysadmin') y también lo deja sin conexión a la base de datos.
  • Si NO ha iniciado sesión como administrador, la llamada RAISEERROR () fallará y el script continuará ejecutándose .
  • Cuando se invoca con sqlcmd.exe, se informará el código de salida 2745.

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

El método noexec

Otro método que funciona con las declaraciones GO es set noexec on. Esto hace que se omita el resto del script. No termina la conexión, pero debe noexecapagarse nuevamente antes de que se ejecute cualquier comando.

Ejemplo:

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á fuera
fuente
14
¡Eso es genial! Es un enfoque de "gran palo", pero hay momentos en que realmente lo necesitas. Tenga en cuenta que requiere la gravedad 20 (o superior) y "CON REGISTRO".
Rob Garrison
55
Tenga en cuenta que con el método noexec, el resto del script todavía se interpreta, por lo que aún obtendrá errores en tiempo de compilación, como la columna no existe. Si desea tratar condicionalmente los cambios de esquema conocidos que involucran columnas faltantes omitiendo algún código, la única forma en que sé hacerlo es usar: r en modo sqlcommand para hacer referencia a archivos externos.
David Eison
20
Lo noexec es genial. ¡Muchas gracias!
Gaspa79
2
"Esto terminará la conexión", parece que no, al menos eso es lo que estoy viendo.
jcollum
66
Estaba probando este método y no obtuve el resultado correcto cuando me di cuenta ... Solo hay una E en raiserror ...
bobkingof12vs
187

Simplemente use un RETORNO (funcionará tanto dentro como fuera de un procedimiento almacenado).

Gordon Bell
fuente
2
Por alguna razón, estaba pensando que el retorno no funcionaba en los scripts, ¡pero lo intenté y funciona! Gracias
Andy White
44
En un script, no puede hacer un RETORNO con un valor como puede hacerlo en un procedimiento almacenado, pero puede hacer un RETORNO.
Rob Garrison
53
No, solo termina hasta el próximo GO. El siguiente lote (después del GO) se ejecutará como de costumbre
Mortb
2
peligroso asumir ya que continuará después del próximo GO.
Justin
1
GO es un terminador o delimitador de script; No es un código SQL. GO es solo una instrucción para el cliente que está utilizando para enviar un comando al motor de la base de datos de que se está iniciando un nuevo script después del delimitador GO.
Ingeniero invertido
50

Si puede usar el modo SQLCMD, entonces el encantamiento

:on error exit

(INCLUYENDO los dos puntos) hará que RAISERROR detenga realmente el script. P.ej,

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

dará salida:

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

y el lote se detendrá. Si el modo SQLCMD no está activado, obtendrá un error de análisis sobre los dos puntos. Desafortunadamente, no es completamente a prueba de balas, ya que si el script se ejecuta sin estar en modo SQLCMD, ¡SQL Managment Studio no pasa por alto los errores de tiempo de análisis! Aún así, si los está ejecutando desde la línea de comandos, está bien.

Gafas de sol
fuente
44
Gran comentario, gracias. Agregaré que en el modo SSMS SQLCmd se alterna bajo el menú Consulta.
David Peters
esto es útil - significa que no necesita la opción -b cuando se ejecuta
JonnyRaa
2
luego el encantamiento ... pero ¿cómo lanzo Magic Missle?
JJS
1
Perfecto. no requiere derechos de usuario sysadmin ultra extra
Pac0
21

No usaría RAISERROR: SQL tiene instrucciones IF que se pueden usar para este propósito. Haga su validación y búsquedas y establezca las variables locales, luego use el valor de las variables en las declaraciones IF para hacer que las inserciones sean condicionales.

No necesitaría verificar un resultado variable de cada prueba de validación. Por lo general, puede hacer esto con solo una variable de indicador para confirmar que se pasaron todas las condiciones:

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

Incluso si su validación es más compleja, solo debería necesitar algunas variables de marca para incluir en su (s) verificación (es) final (es).

Dave Swersky
fuente
Sí, estoy usando IFs en otras partes del script, pero no quiero tener que verificar cada variable local antes de intentar hacer una inserción. Prefiero que se detenga todo el script y obligue al usuario a verificar las entradas. (Esto es solo un guión rápido y sucio)
Andy White
44
No estoy muy seguro de por qué esta respuesta se ha marcado porque es técnicamente correcta, pero no es lo que el cartel "quiere" hacer.
John Sansom
¿Es posible tener múltiples bloques dentro de Begin..End? DECLARACIÓN de significado; VAMOS; DECLARACIÓN; VAMOS; ¿etcétera etcétera? Recibo errores y supongo que esa podría ser la razón.
Nenotlep
3
Esto es mucho más confiable que RAISERROR, especialmente si no sabe quién ejecutará los scripts y con qué privilegios.
Cypher
@John Sansom: El único problema que veo aquí es que la declaración IF no funciona si está intentando pasar a una declaración GO. Este es un gran problema si sus scripts se basan en las declaraciones GO (por ejemplo, las declaraciones DDL). Aquí hay un ejemplo que funciona sin la primera instrucción 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

En SQL 2012+, puede usar THROW .

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

De MSDN:

Provoca una excepción y transfiere la ejecución a un bloque CATCH de una construcción TRY ... CATCH ... Si una construcción TRY ... CATCH no está disponible, la sesión finaliza. Se establece el número de línea y el procedimiento donde se genera la excepción. La gravedad se establece en 16.

Jordan Parker
fuente
1
THROW está destinado a reemplazar RAISERROR, pero no puede evitar lotes posteriores en el mismo archivo de script con él.
NReilingh
Corregir @NReilingh. Ahí es donde la respuesta de Blorgbeard es realmente la única solución. Sin embargo, requiere sysadmin (nivel de gravedad 20), y es bastante pesado si no hay varios lotes en el script.
Jordan Parker
2
active xact abort si también desea cancelar la transcripción actual.
nurettin
13

Extendí la solución de activación / desactivación de noexec con éxito con una transacción para ejecutar el script de manera todo o nada.

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

Aparentemente, el compilador "comprende" la variable @finished en el IF, incluso si hubo un error y la ejecución fue deshabilitada. Sin embargo, el valor se establece en 1 solo si la ejecución no se deshabilitó. Por lo tanto, puedo comprometer o revertir la transacción en consecuencia.

Tz_
fuente
No entiendo. Seguí las instrucciones. Ingresé el siguiente SQL después de cada GO. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Pero la ejecución nunca se detuvo y terminé con tres errores de "Transacción de retroceso". ¿Algunas ideas?
user1161391
12

puedes envolver tu declaración SQL en un ciclo WHILE y usar BREAK si es necesario

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
fuente
55
Me gusta el aspecto de esto, parece un poco mejor que generar un error. ¡Definitivamente no quiero olvidar el descanso al final!
Andy White
1
También podría usar una variable e inmediatamente establecerla en la parte superior del bucle para evitar la "división". DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; ENDMás detallado, pero diablos, de todos modos es TSQL ;-)
Así es como algunas personas realizan goto, pero es más confuso seguirlo que goto.
nurettin
Este enfoque protege de un GO ocasional inesperado. Agradecido.
it3xl
10

Puede alterar el flujo de ejecución usando declaraciones GOTO :

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

/* our code */

EndScript:
Charlie
fuente
2
Usar goto es una forma aceptable de manejar la excepción. Reduce la cantidad de variables y anidamiento y no causa una desconexión. Probablemente sea preferible al manejo de excepciones arcaico que permiten las secuencias de comandos de SQL Server.
Antonio Drusin el
Al igual que TODAS las otras sugerencias aquí, esto no funciona si "nuestro código" contiene una declaración "GO".
Mike Gledhill
9

Refinando aún más el método Sglasses, las líneas anteriores fuerzan el uso del modo SQLCMD, y trincan el scirpt si no usan el modo SQLCMD o lo usan :on error exitpara salir de cualquier error
CONTEXT_INFO se usa para realizar un seguimiento del estado.

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-------------
revs jaraics
fuente
2
Esta es la única forma que encontré para evitar la locura de SSMS de no poder abortar el script. Pero agregué 'SET NOEXEC OFF' al principio y 'SET NOEXEC ON' si no está en modo SQLCMD, de lo contrario el script real continuará a menos que levantes un error en el nivel 20 con log.
Mark Sowul
8

¿Es este un procedimiento almacenado? Si es así, creo que podría hacer una devolución, como "Devolver NULL";

mtazva
fuente
Gracias por la respuesta, es bueno saberlo, pero en este caso no es un proceso almacenado, solo un archivo de script
Andy White
1
@ Gordon No siempre (aquí estoy buscando). Ver otras respuestas (GO se dispara, por un lado)
Mark Sowul
6

Sugeriría que envuelva su bloque de código apropiado en un bloque try catch. Luego puede usar el evento Raiserror con una gravedad de 11 para ir al bloque catch si lo desea. Si solo desea aumentar los espejos pero continuar la ejecución dentro del bloque de prueba, use una gravedad menor.

¿Tener sentido?

Saludos, John

[Editado para incluir referencia BOL]

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

John Sansom
fuente
Nunca he visto un try-catch en SQL, ¿te importaría publicar un ejemplo rápido de lo que quieres decir?
Andy White
2
es nuevo en 2005. BEGIN TRY {sql_statement | Statement_block} FIN INTENTAR COMENZAR CAPTURA {sql_statement | instrucción_bloque} FIN DE CAPTURA [; ]
Sam
@Andy: Referencia agregada, ejemplo incluido.
John Sansom
2
El bloque TRY-CATCH no permite IR dentro de sí mismo.
AntonK
4

puedes usar RAISERROR .

Mladen Prajdic
fuente
3
Esto no tiene sentido para mí: plantear un error evitable (suponiendo que estamos hablando de validación referencial aquí) es una forma horrible de hacerlo si la validación es posible antes de que se realicen las inserciones.
Dave Swersky
2
raiserror puede usarse como un mensaje informativo con una configuración de gravedad baja.
Mladen Prajdic
2
El script continuará a menos que se cumplan ciertas condiciones establecidas en la respuesta aceptada.
Eric J.
4

Ninguno de estos trabajos con declaraciones 'GO'. En este código, independientemente de si la gravedad es 10 u 11, se obtiene la sentencia PRINT final.

Script de prueba:

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

Resultados:

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 única forma de hacer que esto funcione es escribir el script sin GOdeclaraciones. A veces eso es fácil. A veces es bastante difícil. (Usa algo como IF @error <> 0 BEGIN ...)

Rob Garrison
fuente
No puedo hacer eso con CREAR PROCEDIMIENTO, etc. Vea mi respuesta para una solución.
Blorgbeard sale el
La solución de Blogbeard es genial. He estado trabajando con SQL Server durante años y esta es la primera vez que veo esto.
Rob Garrison
4

Lo uso RETURNaquí todo el tiempo, funciona en script oStored Procedure

Asegúrese de realizar ROLLBACKla transacción si está en una, de lo contrario, de RETURNinmediato se generará una transacción abierta no comprometida

jerryhung
fuente
55
No funciona con un script que contiene varios lotes (sentencias GO); vea mi respuesta para saber cómo hacerlo.
Blorgbeard sale el
1
RETURN simplemente sale del bloque actual de declaraciones. Si está en un bloque IF END, la ejecución continuará después de END. Esto significa que no puede usar RETURN para finalizar la ejecución después de probar alguna condición, porque siempre estará en el bloque IF END.
Cdonner
3

Esta fue mi solución:

...

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

Puede usar la declaración GOTO. Prueba esto. Este es el uso completo para ti.

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

FinalStatement:
     Select @CoumnName from TableName
Vishal Kiri
fuente
Se supone que GOTO es una mala práctica de codificación, se recomienda el uso de "TRY..CATCH", ya que se introdujo desde SQL Server 2008, seguido de THROW en 2012.
Eddie Kumar
1

Gracias por la respuesta!

raiserror()funciona bien, pero no debe olvidar la returndeclaración, de lo contrario, el script continúa sin errores. (¡hense que el raiserror no es un "lanzador de terror" ;-)) y, por supuesto, hacer un retroceso si es necesario!

raiserror() Es bueno decirle a la persona que ejecuta el script que algo salió mal.


fuente
1

Si simplemente está ejecutando un script en Management Studio y desea detener la ejecución o la operación de reversión (si se usa) en el primer error, entonces la mejor manera que considero es usar el bloque try catch (SQL 2005 en adelante). Esto funciona bien en Management Studio si está ejecutando un archivo de script. El proceso almacenado siempre puede usar esto también.

Bhargav Shah
fuente
1
¿Qué agrega su respuesta a la respuesta aceptada con más de 60 votos a favor? ¿Lo has leído? Consulte esta pregunta de metaSO y Jon Skeet: Coding Blog sobre cómo dar una respuesta correcta.
Yaroslav
0

En el pasado utilizamos lo siguiente ... funcionó mejor:

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

Adjúntelo en un bloque try catch, luego la ejecución se transferirá a 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
fuente