SQL Server - zatrzymuje lub przerywa wykonywanie skryptu SQL


325

Czy istnieje sposób, aby natychmiast zatrzymać wykonywanie skryptu SQL na serwerze SQL, na przykład polecenie „break” lub „exit”?

Mam skrypt, który wykonuje sprawdzanie poprawności i wyszukiwania, zanim zacznie wstawiać, i chcę, aby przestał, jeśli którekolwiek z sprawdzeń poprawności lub odnośników zakończy się niepowodzeniem.

Odpowiedzi:


371

RAISERROR Sposób

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

Spowoduje to zakończenie połączenia, a tym samym zatrzymanie pozostałej części skryptu.

Należy pamiętać, że zarówno poziom ważności 20 lub wyższy, jak i WITH LOGopcja są konieczne, aby działał w ten sposób.

Działa to nawet z instrukcjami GO, np.

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

Otrzymasz wynik:

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.

Zauważ, że napis „ho” nie jest drukowany.

UWAGI:

  • Działa to tylko wtedy, gdy jesteś zalogowany jako administrator (rola „sysadmin”) i nie pozostawia Cię bez połączenia z bazą danych.
  • Jeśli NIE jesteś zalogowany jako administrator, samo wywołanie RAISEERROR () zakończy się niepowodzeniem i skrypt będzie kontynuował działanie .
  • W przypadku wywołania za pomocą narzędzia sqlcmd.exe zostanie zgłoszony kod wyjścia 2745.

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

Metoda noexec

Inną metodą, która działa z instrukcjami GO, jest set noexec on. Powoduje to pominięcie reszty skryptu. Nie powoduje to zakończenia połączenia, ale należy je noexecponownie wyłączyć przed wykonaniem jakichkolwiek poleceń.

Przykład:

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
To cudownie! Jest to podejście typu „big stick”, ale są chwile, kiedy naprawdę tego potrzebujesz. Pamiętaj, że wymaga to zarówno poziomu ważności 20 (lub wyższego), jak i „WITH LOG”.
Rob Garrison

5
Zauważ, że przy metodzie noexec reszta skryptu jest nadal interpretowana, więc nadal będziesz otrzymywać błędy kompilacji, takie jak brak kolumny. Jeśli chcesz warunkowo poradzić sobie ze znanymi zmianami schematu obejmującymi brakujące kolumny, pomijając jakiś kod, jedynym sposobem, jaki to robię, jest użycie: rw trybie polecenia sql, aby odwoływać się do plików zewnętrznych.
David Eison

20
Noexec jest świetny. Wielkie dzięki!
Gaspa79

2
„To zakończy połączenie” - wydaje się, że tak nie jest, przynajmniej tak właśnie widzę.
jcollum

6
Próbowałem tej metody i nie osiągnąłem właściwego rezultatu, kiedy zdałem sobie sprawę ... Jest tylko jeden E w podbiciu ...
bobkingof12vs 29.01.2016

187

Wystarczy użyć POWROTU (będzie działał zarówno wewnątrz, jak i poza procedurą przechowywaną).


2
Z jakiegoś powodu myślałem, że return nie działa w skryptach, ale po prostu spróbowałem i działa! Dzięki
Andy White

4
W skrypcie nie można wykonać ZWROTU o wartości takiej jak w procedurze składowanej, ale można ZWRÓCIĆ.
Rob Garrison

53
Nie, to kończy się tylko do następnego GO Następna partia (po GO) będzie działać jak zwykle
Mortb

2
zakładać, że będzie to niebezpieczne, ponieważ będzie kontynuowane po następnym GO.
Justin

1
GO to terminator lub ogranicznik skryptu; to nie jest kod SQL. GO to tylko instrukcja dla klienta, którego używasz do wysłania polecenia do silnika bazy danych, że nowy skrypt uruchamia się po ograniczniku GO.
Reversed Engineer

50

Jeśli możesz użyć trybu SQLCMD, to inkantacja

:on error exit

(W TYM dwukropek) spowoduje, że RAISERROR faktycznie zatrzyma skrypt. Na przykład,

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

wyświetli:

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

i partia się zatrzyma. Jeśli tryb SQLCMD nie jest włączony, pojawi się błąd analizowania dwukropka. Niestety, nie jest to całkowicie kuloodporne, tak jakby skrypt był uruchamiany bez pracy w trybie SQLCMD, SQL Managment Studio unosi się tuż obok błędów analizy czasu! Mimo to, jeśli uruchamiasz je z wiersza poleceń, jest w porządku.


4
Świetny komentarz, dzięki. Dodam, że w trybie SSMS SQLCmd przełącza się w menu Zapytanie.
David Peters,

jest to przydatne - oznacza, że ​​nie potrzebujesz opcji -b podczas uruchamiania
JonnyRaa

2
potem zaklęcie ... ale jak rzucić Magiczną Missle ?!
JJS

1
doskonały. nie wymaga sysadmin ultra dodatkowych praw użytkownika
Pac0

21

Nie użyłbym RAISERROR-SQL ma instrukcje JEŻELI, które mogą być użyte do tego celu. Dokonaj walidacji i przeglądów oraz ustaw zmienne lokalne, a następnie użyj wartości zmiennych w instrukcjach IF, aby warunkować wstawki.

Nie trzeba sprawdzać zmiennego wyniku każdego testu walidacyjnego. Zwykle można to zrobić za pomocą tylko jednej zmiennej flagi, aby potwierdzić spełnienie wszystkich warunków:

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

Nawet jeśli sprawdzanie poprawności jest bardziej złożone, potrzebujesz tylko kilku zmiennych flag, aby uwzględnić je w końcowej kontroli.


Tak, używam IF w innych częściach skryptu, ale nie chcę sprawdzać każdej zmiennej lokalnej przed próbą wstawienia. Wolę po prostu zatrzymać cały skrypt i zmusić użytkownika do sprawdzenia danych wejściowych. (To tylko szybki i brudny skrypt)
Andy White,

4
Nie jestem do końca pewien, dlaczego ta odpowiedź została zaznaczona, ponieważ jest to technicznie poprawne, tylko nie to, co plakat „chce” zrobić.
John Sansom,

Czy możliwe jest posiadanie wielu bloków w Begin..End? Znaczenie OŚWIADCZENIE; UDAĆ SIĘ; KOMUNIKAT; UDAĆ SIĘ; itd itd? Pojawiają się błędy i chyba to może być powód.
Nenotlep

3
Jest to o wiele bardziej niezawodne niż RAISERROR, zwłaszcza jeśli nie wiesz, kto będzie uruchamiał skrypty i jakie uprawnienia.
Cypher

@John Sansom: Jedyny problem, jaki tu widzę, polega na tym, że instrukcja IF nie działa, jeśli próbujesz rozgałęzić się na instrukcji GO. Jest to duży problem, jeśli skrypty korzystają z instrukcji GO (np. Instrukcji DDL). Oto przykład, który działa bez pierwszego polecenia 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

W SQL 2012+ możesz użyć THROW .

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

Z MSDN:

Zgłasza wyjątek i przekazuje wykonanie do bloku CATCH konstrukcji TRY… CATCH ... Jeśli konstrukcja TRY… CATCH nie jest dostępna, sesja jest zakończona. Ustawiony jest numer linii i procedura, w której zgłaszany jest wyjątek. Istotność jest ustawiona na 16.


1
RZUT ma zastąpić RAISERROR, ale nie można zapobiec kolejnym partiom w tym samym pliku skryptu.
NReilingh

Prawidłowo @NReilingh. Właśnie tam odpowiedź Blorgbearda jest naprawdę jedynym rozwiązaniem. Wymaga jednak sysadmin (poziom istotności 20) i jest dość ciężki, jeśli w skrypcie nie ma wielu partii.
Jordan Parker

2
włącz xact abort, jeśli chcesz anulować również bieżącą transkację.
nurettin

13

Z powodzeniem rozszerzyłem rozwiązanie włączania / wyłączania noexec o transakcję, aby uruchomić skrypt w sposób „wszystko albo nic”.

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

Najwyraźniej kompilator „rozumie” zmienną @finished w IF, nawet jeśli wystąpił błąd i wykonanie zostało wyłączone. Jednak wartość jest ustawiona na 1 tylko wtedy, gdy wykonanie nie zostało wyłączone. Dlatego mogę odpowiednio zatwierdzić lub wycofać transakcję.


Nie rozumiem. Postępowałem zgodnie z instrukcjami. Wprowadziłem następujący kod SQL po każdym GO. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Ale wykonanie nigdy się nie zatrzymało i skończyło się na trzech błędach „wycofywania transakcji”. Jakieś pomysły?
user1161391,

12

możesz zawinąć instrukcję SQL w pętlę WHILE i użyć BREAK, jeśli to konieczne

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
Wygląda mi na to, że wygląda to trochę ładniej niż zgłaszanie błędów. Zdecydowanie nie chcę zapomnieć o przerwie na końcu!
Andy White,

1
Możesz także użyć zmiennej i natychmiast ustawić ją na górze pętli, aby uniknąć „podziału”. DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; ENDBardziej gadatliwy, ale do cholery, to i tak TSQL ;-)

W ten sposób niektórzy wykonują goto, ale jest bardziej mylące niż goto.
nurettin

Takie podejście chroni przed nieoczekiwanym GO. Mający zrozumienie.
it3xl

10

Możesz zmienić przebieg wykonywania za pomocą instrukcji GOTO :

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

/* our code */

EndScript:

2
użycie goto jest akceptowalnym sposobem obsługi wyjątku. Zmniejsza liczbę zmiennych i zagnieżdżanie i nie powoduje rozłączenia. Prawdopodobnie jest to lepsze niż archaiczna obsługa wyjątków, na którą pozwalają skrypty SQL Server.
Antonio Drusin,

Podobnie jak WSZYSTKIE inne sugestie tutaj, nie działa to, jeśli „nasz kod” zawiera instrukcję „GO”.
Mike Gledhill

9

Dalsze udoskonalenie metody Sglasses, powyższe linie wymuszają użycie trybu SQLCMD i albo skończą scirpt, jeśli nie używają trybu SQLCMD, albo wykorzystują :on error exitwyjście z dowolnego błędu.
CONTEXT_INFO służy do śledzenia stanu.

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
To jedyny sposób, w jaki znalazłem obejście szaleństwa SSMS polegającego na niemożności przerwania skryptu. Ale na początku dodałem „SET NOEXEC OFF” i „SET NOEXEC ON”, jeśli nie jest w trybie SQLCMD, w przeciwnym razie rzeczywisty skrypt będzie działał, chyba że zgłosisz błąd na poziomie 20 z logiem.
Mark Sowul

8

Czy to jest procedura składowana? Jeśli tak, myślę, że możesz po prostu wykonać Zwrot, na przykład „Zwróć NULL”;


Dzięki za odpowiedź, dobrze wiedzieć, ale w tym przypadku nie jest to zapisany proc, tylko plik skryptu
Andy White

1
@Gordon Nie zawsze (tutaj szukam). Zobacz inne odpowiedzi (GO podchodzi, na przykład)
Mark Sowul

6

Sugerowałbym zawinięcie odpowiedniego bloku kodu w blok try catch. Następnie możesz użyć zdarzenia Raiserror o sile 11, aby przełamać blok catch, jeśli chcesz. Jeśli chcesz tylko podnieść błędy, ale kontynuować wykonywanie w bloku try, użyj niższego poziomu ważności.

Ma sens?

Na zdrowie, John

[Edytowane w celu uwzględnienia BOL Reference]

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


Nigdy nie widziałem próbowania w SQL - czy mógłbyś opublikować szybki przykład tego, co masz na myśli?
Andy White,

2
jest nowością do 2005 roku. ROZPOCZNIJ SPRÓBUJ {sql_statement | Statement_block} KONIEC SPRÓBUJ POCZĄTEK ŁOWIENIA {sql_statement | Statement_block} KONIEC POŁOWU [; ]
Sam

@Andy: Dodano odniesienie, uwzględniono przykład.
John Sansom

2
Blok TRY-CATCH nie pozwala GO wewnątrz siebie.
AntonK

4

możesz użyć RAISERROR .


3
To nie ma sensu zgłaszanie błędu, którego można uniknąć (zakładając, że mówimy tutaj o sprawdzaniu poprawności referencji) jest okropnym sposobem, aby to zrobić, jeśli sprawdzanie poprawności jest możliwe przed wstawieniem.
Dave Swersky

2
Grozę podniesienia można wykorzystać jako komunikat informacyjny o niskim poziomie istotności.
Mladen Prajdic

2
Skrypt będzie kontynuowany, chyba że zostaną spełnione określone warunki w zaakceptowanej odpowiedzi.
Eric J.

4

Żadne z tych nie działa z instrukcjami „GO”. W tym kodzie, niezależnie od tego, czy wskaźnik ważności wynosi 10 czy 11, otrzymujesz ostateczną instrukcję PRINT.

Skrypt testowy:

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

Wyniki:

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

Jedynym sposobem, aby to zadziałało, jest napisanie skryptu bez GOinstrukcji. Czasami to łatwe. Czasami jest to dość trudne. (Użyj czegoś takiego IF @error <> 0 BEGIN ....)


Nie można tego zrobić za pomocą polecenia CREATE PROCEDURE itp. Zobacz moją odpowiedź na rozwiązanie.
Blorgbeard wyszedł

Rozwiązanie Blogbearda jest świetne. Pracuję z SQL Server od lat i po raz pierwszy to widzę.
Rob Garrison,

4

Korzystam RETURNtu cały czas, pracuje w skrypcie lubStored Procedure

Upewnij się, że ROLLBACKtransakcja, jeśli jesteś w jednym, w przeciwnym razie RETURNnatychmiast spowoduje otwartą, niezaangażowaną transakcję


5
Nie działa ze skryptem zawierającym wiele partii (instrukcje GO) - zobacz moją odpowiedź, jak to zrobić.
Blorgbeard jest poza

1
RETURN właśnie wychodzi z bieżącego bloku instrukcji. Jeśli znajdujesz się w bloku IF END, wykonywanie będzie kontynuowane po zakończeniu END. Oznacza to, że nie można użyć polecenia RETURN, aby zakończyć wykonywanie po przetestowaniu dla określonego warunku, ponieważ zawsze będzie się znajdować w bloku IF END.
cdonner


3

Możesz użyć instrukcji GOTO. Spróbuj tego. To jest pełne wykorzystanie dla ciebie.

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

FinalStatement:
     Select @CoumnName from TableName

GOTO ma być złą praktyką kodowania, zaleca się użycie „TRY..CATCH”, ponieważ wprowadzono je od SQL Server 2008, a następnie w THROW w 2012 r.
Eddie Kumar

1

Dziękuję za odpowiedź!

raiserror()działa dobrze, ale nie należy zapominać o returninstrukcji, w przeciwnym razie skrypt będzie działał bezbłędnie! (henser ten nie jest „thrrowrorem” ;-)) i oczywiście w razie potrzeby wycofuje się!

raiserror() miło jest powiedzieć osobie wykonującej skrypt, że coś poszło nie tak.


1

Jeśli po prostu wykonujesz skrypt w Management Studio i chcesz zatrzymać wykonanie lub wycofanie transakcji (jeśli jest używana) przy pierwszym błędzie, to moim zdaniem najlepszym sposobem jest użycie try catch block (SQL 2005 i nowsze). Działa to dobrze w Management studio, jeśli wykonujesz plik skryptu. Przechowywany proc może również z niego korzystać.


1
Co twoja odpowiedź dodaje do zaakceptowanej odpowiedzi przy ponad 60 głosach pozytywnych? Czytałeś to? Sprawdź to pytanie metaSO i Jona Skeeta: Blog kodowania, jak udzielić poprawnej odpowiedzi.
Yaroslav

0

Wcześniej używaliśmy następujących ... działało najlepiej:

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

0

Umieść go w bloku try catch, a następnie wykonanie zostanie przekazane do złapania.

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;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.