SQL Server - interrompe ou interrompa a execução de um script SQL

325

Existe uma maneira de interromper imediatamente a execução de um script SQL no SQL server, como um comando "break" ou "exit"?

Eu tenho um script que faz algumas validações e pesquisas antes de começar a inserir, e quero que pare se alguma das validações ou pesquisas falhar.

Andy White
fonte

Respostas:

371

O método raiserror

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

Isso encerrará a conexão, interrompendo a execução do restante do script.

Observe que o nível de gravidade 20 ou superior e a WITH LOGopção são necessárias para que funcione dessa maneira.

Isso funciona mesmo com instruções GO, por exemplo.

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

Dará a você a saída:

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.

Observe que 'ho' não é impresso.

RESSALVAS:

  • Isso funciona apenas se você estiver logado como administrador (função 'sysadmin') e também deixa você sem conexão com o banco de dados.
  • Se você NÃO estiver logado como administrador, a chamada RAISEERROR () falhará e o script continuará sendo executado .
  • Quando chamado com sqlcmd.exe, o código de saída 2745 será relatado.

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

O método noexec

Outro método que funciona com instruções GO é set noexec on. Isso faz com que o restante do script seja pulado. Não encerra a conexão, mas você precisa ativarnoexec desligar novamente antes que qualquer comando seja executado.

Exemplo:

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á fora
fonte
14
Fantástico! É um pouco de abordagem "big stick", mas há momentos em que você realmente precisa. Observe que ele requer a gravidade 20 (ou superior) e "WITH LOG".
Rob Garrison
5
Observe que, com o método noexec, o restante do script ainda é interpretado; portanto, você ainda receberá erros em tempo de compilação, como a coluna não existe. Se você deseja lidar condicionalmente com alterações de esquema conhecidas envolvendo colunas ausentes, ignorando algum código, a única maneira que sei fazer é usar: r no modo sqlcommand para referenciar arquivos externos.
David Eison 31/03
20
A coisa noexec é ótima. Muito obrigado!
precisa saber é o seguinte
2
"Isso encerrará a conexão" - parece que não, pelo menos é o que estou vendo.
Jcollum # 9/14
6
Eu estava tentando este método e não obtendo resultado certo quando eu percebi ... Há apenas um E no em raiserror ...
bobkingof12vs
187

Basta usar um RETURN (ele funcionará dentro e fora de um procedimento armazenado).

Gordon Bell
fonte
2
Por alguma razão, eu estava pensando que o retorno não funcionava em scripts, mas eu apenas tentei, e funciona! Obrigado
Andy White
4
Em um script, você não pode fazer um RETURN com um valor como em um procedimento armazenado, mas pode fazer um RETURN.
Rob Garrison
53
Não, ele só termina até o próximo GO O próximo lote (após o GO) será executado como de costume
mortb 5/13/13
2
perigoso assumir, pois continuará depois do próximo GO.
23716 Justin
1
GO é um terminador ou delimitador de script; não é código SQL. GO é apenas uma instrução para o cliente que você está usando para enviar um comando ao mecanismo de banco de dados de que um novo script está iniciando após o delimitador GO.
Engenheiro Reverso
50

Se você pode usar o modo SQLCMD, o encantamento

:on error exit

(INCLUINDO os dois pontos) fará com que RAISERROR realmente pare o script. Por exemplo,

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

irá produzir:

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 o lote irá parar. Se o modo SQLCMD não estiver ativado, você receberá um erro de análise sobre os dois pontos. Infelizmente, não é completamente à prova de balas, como se o script fosse executado sem estar no modo SQLCMD, o SQL Managment Studio passa rapidamente até mesmo analisando erros de tempo! Ainda assim, se você estiver executando-os na linha de comando, tudo bem.

Óculos de sol
fonte
4
Ótimo comentário, obrigado. Acrescentarei que no modo SSMS SQLCmd é alternado no menu Consulta.
David Peters
isto é útil - significa que você não precisa a opção -b ao executar
JonnyRaa
2
então o encantamento ... mas como faço para lançar Magic Missle ?!
JJS 3/02
1
perfeito. não requer sysadmin direitos de usuário ultra-extras
Pac0
21

Eu não usaria instruções RAISERROR-SQL has IF que podem ser usadas para esse fim. Faça sua validação e pesquisas e defina variáveis ​​locais e, em seguida, use o valor das variáveis ​​nas instruções IF para tornar as inserções condicionais.

Você não precisaria verificar um resultado variável de cada teste de validação. Você geralmente pode fazer isso com apenas uma variável de sinalizador para confirmar todas as condições aprovadas:

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

Mesmo que sua validação seja mais complexa, você precisará apenas de algumas variáveis ​​de sinalizador para incluir em suas verificações finais.

Dave Swersky
fonte
Sim, estou usando IFs em outras partes do script, mas não quero verificar todas as variáveis ​​locais antes de tentar inserir. Prefiro parar o script inteiro e forçar o usuário a verificar as entradas. (Este é apenas um script rápido e sujo)
Andy White
4
Não sei ao certo por que essa resposta foi marcada porque é tecnicamente correta, mas não o que o pôster "quer" fazer.
John Sansom
É possível ter vários blocos no Begin..End? Significado DECLARAÇÃO; IR; DECLARAÇÃO; IR; etc etc? Estou recebendo erros e acho que esse pode ser o motivo.
Nenotlep
3
Isso é muito mais confiável do que RAISERROR, especialmente se você não sabe quem executará os scripts e com quais privilégios.
Cypher
@ John Sansom: O único problema que vejo aqui é que a instrução IF não funciona se você estiver tentando ramificar uma instrução GO. Este é um grande problema se seus scripts confiarem nas instruções GO (por exemplo, instruções DDL). Aqui está um exemplo que funciona sem a primeira instrução 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

No SQL 2012+, você pode usar o THROW .

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

Do MSDN:

Gera uma exceção e transfere a execução para um bloco CATCH de uma construção TRY… CATCH ... Se uma construção TRY ... CATCH não estiver disponível, a sessão será encerrada. O número da linha e o procedimento em que a exceção é gerada são definidos. A gravidade está definida como 16.

Jordan Parker
fonte
1
THROW pretende substituir RAISERROR, mas você não pode impedir lotes subsequentes no mesmo arquivo de script.
NReilingh
Corrija @NReilingh. É aí que a resposta de Blorgbeard é realmente a única solução. Porém, requer sysadmin (nível de gravidade 20) e é bastante pesado se não houver vários lotes no script.
Jordan Parker
2
ative o cancelamento do xact se quiser cancelar também a transcensão atual.
precisa saber é
13

Estendi com êxito a solução onexec on / off com uma transação para executar o script de uma maneira tudo ou 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, o compilador "entende" a variável @finished no IF, mesmo se houver um erro e a execução estiver desabilitada. No entanto, o valor é definido como 1 somente se a execução não foi desativada. Portanto, posso confirmar ou reverter a transação adequadamente.

Tz_
fonte
Eu não entendi. Eu segui as instruções. Entrei no SQL a seguir após cada GO. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Mas a execução nunca parou e acabei com os três erros de "Reversão da transação" gerados. Alguma ideia?
user1161391
12

você pode agrupar sua instrução SQL em um loop WHILE e usar BREAK, se necessário

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
fonte
5
Eu meio que gosto da aparência disso, parece um pouco melhor do que aumentar o erro. Definitivamente, não quero esquecer a pausa no final!
Andy White
1
Você também pode usar uma variável e defini-la imediatamente na parte superior do loop para evitar a "divisão". DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; ENDMais detalhado, mas que diabo, é TSQL qualquer maneira ;-)
É assim que algumas pessoas realizam o goto, mas é mais confuso seguir do que o goto.
Nurettin
Essa abordagem protege contra um GO ocasional inesperado. Apreciativo.
it3xl
10

Você pode alterar o fluxo de execução usando instruções GOTO :

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

/* our code */

EndScript:
Charlie
fonte
2
usar goto é uma maneira aceitável de lidar com exceções. Reduz a quantidade de variáveis ​​e o aninhamento e não causa uma desconexão. Provavelmente é preferível à manipulação arcaica de exceções permitida pelos scripts do SQL Server.
Antonio Drusin
Assim como TODAS as outras sugestões aqui, isso não funciona se "nosso código" contiver uma instrução "GO".
Mike Gledhill
9

Refinando ainda mais o método Sglasses, as linhas acima forçam o uso do modo SQLCMD e limitam o escopo se não estiver usando o modo SQLCMD ou usam :on error exitpara sair de qualquer erro
CONTEXT_INFO é usado para acompanhar o 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-------------
jaraics
fonte
2
Essa é a única maneira que encontrei para solucionar a loucura do SSMS de não poder abortar o script. Mas eu adicionei 'SET NOEXEC OFF' no início e 'SET NOEXEC ON' se não estiver no modo SQLCMD; caso contrário, o script real continuará a menos que você gere um erro no nível 20 com o log.
21812 Mark Sowul
8

Este é um procedimento armazenado? Nesse caso, acho que você poderia simplesmente fazer um retorno, como "Return NULL";

mtazva
fonte
Obrigado pela resposta, que é bom saber, mas neste caso não é uma proc armazenado, apenas um arquivo de script
Andy White
1
@Gordon Nem sempre (aqui estou pesquisando). Veja outras respostas (GO viagens lo, para uma coisa)
Mark Sowul
6

Sugiro que você agrupe seu bloco de código apropriado em um bloco try catch. Em seguida, você pode usar o evento Raiserror com uma gravidade de 11 para interromper o bloco catch, se desejar. Se você deseja apenas aumentar os espelhos, mas continuar a execução no bloco try, use uma gravidade mais baixa.

Faz sentido?

Cheers, John

[Editado para incluir referência BOL]

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

John Sansom
fonte
Eu nunca vi um try-catch no SQL - você se importaria de postar um exemplo rápido do que você quer dizer?
Andy White
2
é novo em 2005. BEGIN TRY {sql_statement | statement_block} END TENTAR INICIAR CAPTURA {sql_statement | statement_block} END CATCH [; ]
Sam
@ Andy: Referência adicionada, exemplo incluído.
John Sansom
2
O bloco TRY-CATCH não permite GO dentro de si.
AntonK
4

você pode usar o RAISERROR .

Mladen Prajdic
fonte
3
Isso não faz sentido para o aumento de um erro evitável (supondo que estamos falando sobre validação referencial aqui) é uma maneira horrível de fazer isso se a validação for possível antes que as inserções ocorram.
Dave Swersky
2
raiserror pode ser usado como uma mensagem informativa com uma configuração de baixa gravidade.
Mladen Prajdic 18/03/2009
2
O script continuará, a menos que certas condições estabelecidas na resposta aceita sejam atendidas.
Eric J.
4

Nenhum desses trabalhos com instruções 'GO'. Nesse código, independentemente de a gravidade ser 10 ou 11, você obtém a instrução PRINT final.

Script de teste:

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

A única maneira de fazer isso funcionar é escrever o script sem GOinstruções. Às vezes é fácil. Às vezes é bem difícil. (Use algo parecido IF @error <> 0 BEGIN ....)

Rob Garrison
fonte
Não é possível fazer isso com CREATE PROCEDURE etc. Veja minha resposta para uma solução.
Blorgbeard saiu em 29/04/09
A solução do Blogbeard é ótima. Trabalho com o SQL Server há anos e é a primeira vez que vejo isso.
Rob Garrison
4

Eu uso RETURNaqui o tempo todo, trabalha em script ouStored Procedure

Verifique se ROLLBACKa transação está em uma, caso contrário, RETURNimediatamente resultará em uma transação não confirmada aberta

jerryhung
fonte
5
Não funciona com um script que contém vários lotes (instruções GO) - veja minha resposta sobre como fazer isso.
Blorgbeard saiu em 29/04/09
1
RETURN apenas sai do bloco atual de instruções. Se você estiver em um bloco IF END, a execução continuará após o END. Isso significa que você não pode usar RETURN para finalizar a execução após o teste de alguma condição, porque você sempre estará no bloco IF END.
Cdonner 15/05
3

Esta foi a minha solução:

...

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

Você pode usar a instrução GOTO. Tente isso. Este é o uso completo para você.

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

FinalStatement:
     Select @CoumnName from TableName
Vishal Kiri
fonte
GOTO é suposto ser uma má codificação prática, o uso de "try..catch" é recomendado, uma vez que foi introduzido desde o SQL Server 2008, seguido por jogar em 2012.
Eddie Kumar
1

Thx pela resposta!

raiserror()funciona bem, mas você não deve esquecer a returndeclaração, caso contrário, o script continua sem erros! (observe que o raiserror não é um "throwerror" ;-)) e, é claro, fazendo uma reversão, se necessário!

raiserror() é bom dizer à pessoa que executa o script que algo deu errado.


fonte
1

Se você está simplesmente executando um script no Management Studio e deseja interromper a execução ou a transação de reversão (se usada) no primeiro erro, a melhor maneira que eu acho é usar o bloco try try (SQL 2005 em diante). Isso funciona bem no Management studio se você estiver executando um arquivo de script. O proc armazenado sempre pode usar isso também.

Bhargav Shah
fonte
1
O que sua resposta adiciona à resposta aceita com mais de 60 votos positivos? Você leu isso? Verifique esta pergunta do metaSO e Jon Skeet: Coding Blog sobre como dar uma resposta correta.
11338 Yaroslav
0

No dia em que usamos o seguinte ... funcionou melhor:

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

Coloque-o em um bloco try catch, então a execução será transferida para 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
fonte