SQL Server - зупинка або припинення виконання сценарію SQL


325

Чи є спосіб негайно зупинити виконання SQL-скрипту на SQL-сервері, як-от команда "break" або "exit"?

У мене є сценарій, який робить деяку перевірку та пошук, перш ніж він починає робити вставки, і я хочу, щоб він припинився, якщо будь-яка перевірка або пошук не вдалася.

Відповіді:


371

RAISERROR метод

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

Це припинить з'єднання, тим самим зупиняючи роботу решти сценарію.

Зауважте, що і рівень тяжкості 20 або вище, і WITH LOGваріант необхідні, щоб він працював таким чином.

Це навіть працює з заявами GO, наприклад.

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

Дасть вам вихід:

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.

Зауважте, що "ho" не друкується.

ПЕРЕМОГИ:

  • Це працює лише в тому випадку, якщо ви зареєстровані як адміністратор (роль 'sysadmin'), а також не залишаєте підключення до бази даних.
  • Якщо ви НЕ увійшли в систему як адміністратор, сам виклик RAISEERROR () завершиться невдало, і сценарій буде продовжувати виконуватись .
  • Після виклику sqlcmd.exe буде повідомлено про код виходу 2745.

Довідка: http://www.mydatabasesupport.com/forums/ms-sqlserver/174037-sql-server-2000-abort-whole-script.html#post761334

Метод noexec

Інший метод, який працює з GO твердженнями, це set noexec on. Це спричиняє пропуск решти сценарію. Це з'єднання не припиняється, але його потрібно noexecвимкнути ще раз, перш ніж виконуватимуться будь-які команди.

Приклад:

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
Це круто! Це трохи підхід "великої палички", але бувають випадки, коли це вам справді потрібно. Зауважте, що вона вимагає як суворості 20 (або вище), так і "З ЛОГОМ".
Роб Гаррісон

5
Зауважте, що за допомогою методу noexec решта сценарію все ще інтерпретується, тому ви все одно отримаєте помилки під час компіляції, наприклад, стовпець не існує. Якщо ви хочете умовно мати справу з відомими змінами схеми, що стосуються відсутніх стовпців, пропускаючи якийсь код, єдиний я знаю, як це зробити, це використовувати: r у режимі sqlcommand для посилання на зовнішні файли.
Девід Ейсон

20
Річ noexec - це чудово. Дуже дякую!
Gaspa79

2
"Це припинить зв'язок" - здається, що цього немає, принаймні, це я бачу.
jcollum

6
Я намагався цей метод і не отримував правильного результату, коли зрозумів ... Є лише один Е в рейдеррорі ...
bobkingof12vs

187

Просто використовуйте ПОВЕРНЕННЯ (воно буде працювати як всередині, так і зовні збереженої процедури).


2
Чомусь я думав, що повернення не працює в сценаріях, але я просто спробував це, і це так! Спасибі
Енді Уайт

4
У сценарії ви не можете виконати ПОВЕРНЕННЯ зі значенням, як ви можете, у збереженій процедурі, але ви можете зробити ПОВЕРНЕННЯ.
Роб Гаррісон

53
Ні, вона закінчується лише до наступного GO Наступна партія (після GO) буде працювати як завжди
mortb

2
небезпечно вважати, що це триватиме і після наступного ГО.
Джастін

1
GO - термінатор або роздільник сценарію; це не SQL-код. GO - це лише інструкція для клієнта, якого ви використовуєте, щоб відправити команду в двигун бази даних, що після розмежувача GO починається новий сценарій.
Інвертор з

50

Якщо ви можете користуватися режимом SQLCMD, тоді заповідник

:on error exit

(Включаючи двокрапки) призведе до того, що RAISERROR фактично зупинить сценарій. Наприклад,

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

виведе:

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

і партія зупиниться. Якщо режим SQLCMD не увімкнено, ви отримаєте помилку розбору щодо двокрапки. На жаль, це не зовсім куленебезпечно, ніби сценарій запускається, не перебуваючи в режимі SQLCMD, студія управління SQL проходить через повний навіть час розбору помилок! Але якщо ви запускаєте їх з командного рядка, це добре.


4
Чудовий коментар, спасибі Додам, що в режимі SSMS SQLCmd перемикається під меню запитів.
Девід Пітерс

це корисно - значить, вам не потрібен варіант -b під час запуску
JonnyRaa

2
то заклинання ... але як я можу закинути Чарівну місл ?!
JJS

1
досконалий. не вимагає sysadmin ультра додаткових прав користувачів
Pac0

21

Я б не використовував RAISERROR- SQL має IF заяви, які можна використовувати для цієї мети. Зробіть валідацію та пошук і встановіть локальні змінні, а потім використовуйте значення змінних у операторах IF, щоб зробити умовні вставки.

Вам не потрібно перевіряти змінний результат кожного тесту перевірки. Зазвичай ви можете зробити це лише однією змінною прапорця, щоб підтвердити всі умови, що пройшли:

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

Навіть якщо ваша перевірка є більш складною, вам знадобиться лише кілька змінних прапорців, які потрібно включити до остаточної перевірки.


Так, я використовую IF-файли в інших частинах сценарію, але мені не хочеться перевіряти кожну локальну змінну, перш ніж спробувати зробити вставку. Я вважаю за краще просто зупинити весь сценарій і змусити користувача перевірити дані. (Це лише швидкий і брудний сценарій)
Енді Уайт

4
Я не зовсім впевнений, чому ця відповідь була позначена, оскільки це технічно правильно, просто не те, що плакат "хоче".
Джон Сансон

Чи можливо мати кілька блоків протягом Begin..End? ОЗНАЧЕННЯ СЛОВА; ГО; ЗАЯВЛЕННЯ; ГО; тощо тощо? Я отримую помилки, і я думаю, що це може бути причиною.
Ненотлеп

3
Це набагато надійніше RAISERROR, особливо якщо ви не знаєте, хто буде запускати сценарії та які привілеї.
Cypher

@John Sansom: Єдина проблема, яку я бачу тут, полягає в тому, що оператор IF не працює, якщо ви намагаєтеся розв'язатись на оператор GO. Це велика проблема, якщо ваші сценарії покладаються на заяви GO (наприклад, оператори DDL). Ось приклад, який працює без першої заяви:declare @i int = 0; if @i=0 begin select '1st stmt in IF block' go end else begin select 'ELSE here' end go
Джеймс Дженсен,

16

У SQL 2012+ ви можете використовувати THROW .

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

Від MSDN:

Підвищує виняток і передає виконання блоку CATCH конструкції TRY… CATCH… Якщо конструкція TRY… CATCH недоступна, сеанс закінчується. Встановлюється номер рядка та порядок, коли піднято виняток. Суворість встановлюється 16.


1
THROW призначений для заміни RAISERROR, але ви не можете запобігти наступним партіям у тому ж файлі сценарію з ним.
NReilingh

Правильно @NReilingh. Ось тут відповідь Блоргберда - це єдине рішення. Однак для цього потрібен sysadmin (рівень серйозності 20), і він досить важкий, якщо в скрипті немає декількох партій.
Йордан Паркер

2
увімкніть xact аборт, якщо ви також хочете скасувати поточну транзакцію.
Нуреттін

13

Я успішно розширив рішення для включення / вимкнення noexec транзакцією для запуску сценарію повністю або нічого.

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

Мабуть, компілятор "розуміє" змінну @finished в ІФ, навіть якщо сталася помилка і виконання було відключено. Однак значення встановлюється на 1, лише якщо виконання не було вимкнено. Отже, я можу відповідним чином здійснити або повернути транзакцію відповідно.


Я не розумію. Я дотримувався вказівок. Я вводив наступний SQL після кожного GO. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Але виконання ніколи не припинялося, і я закінчився трьома помилками "Відхилення трансакції". Будь-які ідеї?
користувач1161391

12

ви можете обернути свій оператор SQL в циклі WHILE і використовувати BREAK, якщо потрібно

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
Мені подобається виглядати це, це здається трохи приємніше, ніж помилка підвищення. Однозначно не хочу забувати перерву в кінці!
Енді Уайт

1
Ви також можете скористатися змінною та негайно встановити її у верхній частині циклу, щоб уникнути "розбиття". DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; ENDБільш докладно, але чорт, це все одно TSQL ;-)

Це те, як деякі люди виконують гото, але це більше заплутано, ніж гото.
Нуреттін

Цей підхід захищає від несподіваних випадкових ГО. Вдячний.
it3xl

10

Ви можете змінити потік виконання за допомогою операторів GOTO :

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

/* our code */

EndScript:

2
використання goto - прийнятний спосіб поводження з винятком. Зменшує кількість змінних та вкладення і не викликає відключення. Це, мабуть, краще для архаїчних обробок виключень, які дозволяє сценарій SQL Server.
Антоніо Друсін

Як і ВСІ інші пропозиції тут, це не працює, якщо "наш код" містить вислів "GO".
Майк Гледхілл

9

Далі метод згущаються Sglasses, вищевказані лінії примусово використовувати режим SQLCMD, і або treminates scirpt , якщо не використовується режим SQLCMD або використовує , :on error exitщоб вийти на якийсь - або помилки
CONTEXT_INFO використовується для відстеження стану.

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
Це єдиний спосіб, коли я виявив, що мені подолати шаленість SSMS за неможливість перервати сценарій. Але я додав "SET NOEXEC OFF" на початку, і "SET NOEXEC ON", якщо не в режимі SQLCMD, інакше власне сценарій буде продовжуватися, якщо ви не зробите помилку на рівні 20 з журналом.
Марк Совул

8

Це збережена процедура? Якщо так, я думаю, що ви могли просто зробити повернення, наприклад, "Return NULL";


Дякую за відповідь, це добре знати, але в даному випадку це не збережений прок, а файл сценарію
Енді Уайт

1
@Gordon Не завжди (тут я шукаю). Дивіться інші відповіді (ПЕРЕГЛЯДУЙТЕ це, до речі)
Марк Совул

6

Я б запропонував вам загорнути відповідний код коду у блок спробувати. Потім ви можете використовувати подію Raiserror з різкістю 11, щоб перейти до блоку спіймання, якщо бажаєте. Якщо ви просто хочете зробити рейнджерс, але продовжуєте виконання у блоці спробу, тоді використовуйте меншу суворість.

Мати сенс?

Ура, Джон

[Відредаговано, щоб включити довідку про BOL]

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


Я ніколи не бачив пробного лову в SQL - ви б не хотіли розмістити короткий приклад того, що маєте на увазі?
Енді Уайт

2
це нове для 2005 року. ПОЧАТИ Спробуйте {sql_statement | statement_block} ЗАКОНЧИЙ ПОЧАТИ ПОЧАТИ {sql_statement | statement_block} END CATCH [; ]
Сем

@Andy: Додано посилання, включений приклад.
Джон Сансон

2
Блок TRY-CATCH не дозволяє зайти в itelf.
AntonK

4

ви можете використовувати RAISERROR .


3
Це не має сенсу міряти помилку, яку можна уникнути (якщо припустити, що ми говоримо тут про референтну перевірку) - це жахливий спосіб зробити це, якщо перевірка можлива до того, як відбудеться вставка.
Дейв Сверський

2
рейзеррор може використовуватися як інформаційне повідомлення з низькою налаштованістю суворості.
Младен Прайдич

2
Сценарій продовжуватиметься, якщо не будуть виконані певні умови, зазначені у прийнятій відповіді.
Ерік Дж

4

Жоден з цих не працює із заявами "GO". У цьому коді, незалежно від того, ступінь тяжкості становить 10 чи 11, ви отримуєте остаточне твердження PRINT.

Тестовий сценарій:

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

Результати:

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

Єдиний спосіб зробити цю роботу - написати сценарій без GOзаяв. Іноді це легко. Іноді це досить складно. (Використовуйте щось подібне IF @error <> 0 BEGIN ....)


Не можу це зробити з CREATE PROCEDURE тощо. Дивіться мою відповідь для рішення.
Blorgbeard вийшов

Рішення Blogbeard - це чудово. Я працював із SQL Server протягом багатьох років, і це я вперше бачив.
Роб Гаррісон

4

Я RETURNтут весь час використовую, працює в сценарії абоStored Procedure

Переконайтеся, що ви ROLLBACKздійснили транзакцію, якщо ви перебуваєте в одній, інакше RETURNнегайно призведе до відкритої несхваленої транзакції


5
Не працює зі сценарієм, що містить кілька пакетів (GO заяви) - дивіться мою відповідь, як це зробити.
Blorgbeard вийшов

1
RETURN просто виходить з поточного блоку висловлювань. Якщо ви знаходитесь у блоці IF END, виконання буде продовжено після END. Це означає, що ви не можете використовувати RETURN для завершення виконання після тестування на певну умову, оскільки ви завжди будете знаходитись у блоці IF END.
cdonner


3

Ви можете використовувати операцію GOTO. Спробуйте це. Це використання для вас повне.

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

FinalStatement:
     Select @CoumnName from TableName

GOTO, як вважається, є поганою практикою кодування, рекомендується використовувати "TRY..CATCH", оскільки він був введений з часу SQL Server 2008, а потім - THROW у 2012 році.
Едді Кумар

1

Thx для відповіді!

raiserror()працює добре, але ви не повинні забувати returnтвердження, інакше сценарій продовжується без помилок! (отже, підйомник не є "меценатом" ;-)) і, звичайно, робити відкат, якщо потрібно!

raiserror() приємно сказати людині, яка виконує сценарій, що щось пішло не так.


1

Якщо ви просто виконуєте скрипт у програмі Management Studio і хочете зупинити виконання або відкат транзакції (якщо використовується) при першій помилці, то найкращим способом, на який я вважаю, є використання блоку спробувати catch (SQL 2005 і далі). Це добре працює в студії менеджменту, якщо ви виконуєте файл сценарію. Збережений Proc завжди може використовувати це також.


1
Що ваша відповідь додає до прийнятої відповіді з 60+ оновленими? Ви читали? Перевірте це запитання щодо metaSO та Jon Skeet: Coding Blog про те, як дати правильну відповідь.
Ярослав

0

Ще в той час, коли ми використовували наступні ... найкраще працювали:

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

0

Прикладіть його до блоку спробу вловлювання, тоді виконання буде переведено на вилов.

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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.