SQL Server - остановить или прервать выполнение сценария SQL

325

Есть ли способ немедленно остановить выполнение SQL-скрипта на SQL-сервере, например, команду «break» или «exit»?

У меня есть сценарий, который выполняет некоторую проверку и поиск до того, как он начнет делать вставки, и я хочу, чтобы он остановился, если какая-либо из проверок или поисков завершится неудачно.

Энди Уайт
источник

Ответы:

371

RAISERROR метод

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

Это прервет соединение, тем самым остановив выполнение остальной части скрипта.

Обратите внимание, что WITH LOGдля его работы необходимы как уровень серьезности 20 или выше, так и опция.

Это даже работает с операторами 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.

Обратите внимание, что «хо» не печатается.

ПРЕДОСТЕРЕЖЕНИЯ:

  • Это работает только в том случае, если вы вошли в систему как администратор (роль '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.
Blorgbeard отсутствует
источник
14
Это потрясающе! Это что-то вроде подхода «большой клюшки», но бывают моменты, когда он вам действительно нужен. Обратите внимание, что для этого требуется как уровень серьезности 20 (или выше), так и «WITH LOG».
Роб Гарнизон
5
Обратите внимание, что при использовании метода noexec остальная часть сценария по-прежнему интерпретируется, поэтому вы по-прежнему будете получать ошибки во время компиляции, например, столбец не существует. Если вы хотите условно работать с известными изменениями схемы, связанными с отсутствующими столбцами, пропуская некоторый код, единственный известный мне способ это сделать: использовать r в режиме sqlcommand для ссылки на внешние файлы.
Дэвид Эйсон
20
Noexec вещь отличная. Большое спасибо!
Gaspa79
2
«Это разорвет связь» - кажется, что нет, по крайней мере, это то, что я вижу.
jcollum
6
Я пробовал этот метод и не получил правильного результата, когда понял: ... В
raiserror
187

Просто используйте RETURN (он будет работать как внутри, так и снаружи хранимой процедуры).

Гордон Белл
источник
2
Почему-то я думал, что return не работает в скриптах, но я просто попробовал, и это работает! Спасибо
Энди Уайт
4
В сценарии вы не можете выполнить RETURN со значением, как в хранимой процедуре, но вы можете сделать RETURN.
Роб Гаррисон
53
Нет, он заканчивается только до следующего GO . Следующая партия (после GO) будет работать как обычно
Mort
2
опасно предполагать, как это будет продолжаться после следующего GO.
Джастин
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 Managment Studio легко справляется даже с ошибками разбора времени! Тем не менее, если вы запускаете их из командной строки, это нормально.

Sglasses
источник
4
Отличный комментарий, спасибо. Добавлю, что в SSMS режим SQLCmd - это переключение под меню Query.
Дэвид Питерс
это полезно - означает, что вам не нужна опция -b при запуске
JonnyRaa
2
тогда заклинание ... но как я могу разыграть Волшебное Мисс?
JJS
1
идеальный. не требует дополнительных прав пользователя sysadmin ultra
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). Вот пример, который работает без первого оператора go: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. Вот где ответ Blorgbeard - действительно единственное решение. Тем не менее, он требует наличия системного администратора (уровень серьезности 20), и он довольно сложен, если в сценарии нет нескольких пакетов.
Джордан Паркер
2
установите xact abort на, если вы хотите отменить текущую транскрипцию.
nurettin
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 в IF, даже если произошла ошибка и выполнение было отключено. Тем не менее, значение устанавливается в 1, только если выполнение не было отключено. Следовательно, я могу соответственно зафиксировать или откатить транзакцию.

Tz_
источник
Я не понимаю. Я следовал инструкциям. Я вводил следующий SQL после каждого GO. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Но выполнение никогда не прекращалось, и я закончил с тремя ошибками «Откат транзакции». Любые идеи?
user1161391
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 в любом случае ;-)
Это то, как некоторые люди выполняют goto, но это более запутанно, чем goto.
nurettin
Этот подход защищает от неожиданного случайного GO. Благодарные.
it3xl
10

Вы можете изменить поток выполнения, используя операторы GOTO :

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

/* our code */

EndScript:
Чарли
источник
2
использование goto является приемлемым способом обработки исключений. Уменьшает количество переменных и вложенности и не вызывает отключения. Вероятно, предпочтительнее архаичной обработки исключений, которую допускают сценарии SQL Server.
Антонио
Как и ВСЕ другие предложения здесь, это не работает, если «наш код» содержит оператор «GO».
Майк Гледхилл
9

Дальнейшее уточнение метода Sglasses, приведенные выше строки вынуждают использовать режим SQLCMD, и либо завершает 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 с log.
Марк Соул
8

Это хранимая процедура? Если это так, я думаю, что вы могли бы просто сделать Return, например «Return NULL»;

mtazva
источник
Спасибо за ответ, это приятно знать, но в данном случае это не сохраненный процесс, а просто файл сценария
Энди Уайт
1
@ Гордон Не всегда (здесь я ищу). Смотрите другие ответы (GO
споткнется, во-первых
6

Я бы посоветовал вам обернуть соответствующий блок кода в блок try catch. Затем вы можете использовать событие Raiserror со степенью серьезности 11, чтобы прорваться к блоку catch, если хотите. Если вы просто хотите вызвать ошибки, но продолжить выполнение в блоке try, используйте меньшую серьезность.

Есть смысл?

Ура, Джон

[Отредактировано для включения BOL Reference]

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

Джон Сансом
источник
Я никогда не видел пробную версию в SQL - не могли бы вы опубликовать быстрый пример того, что вы имеете в виду?
Энди Уайт
2
это новое в 2005 году. НАЧАТЬ ПРОБЫ {sql_statement | Statement_block} END TRY BEGIN CATCH {sql_statement | Statement_block} END CATCH [; ]
Сэм
@ Энди: Ссылка добавлена, пример включен.
Джон Сансом
2
Блок TRY-CATCH не позволяет GO внутри себя.
AntonK
4

Вы можете использовать RAISERROR .

Младен Прайдич
источник
3
Это не имеет смысла, так как указание ошибки, которую можно избежать (если мы говорим здесь о проверке ссылок), является ужасным способом сделать это, если проверка возможна до того, как произойдет вставка.
Дейв Сверски
2
raiserror может использоваться как информационное сообщение с настройкой низкой серьезности.
Младен Прайдич
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немедленно приведет к открытой незафиксированной транзакции

jerryhung
источник
5
Не работает со скриптом, содержащим несколько пакетов (операторы GO) - посмотрите мой ответ, чтобы узнать, как это сделать.
Blorgbeard выходит
1
ВОЗВРАТ просто выходит из текущего блока операторов. Если вы находитесь в блоке IF END, выполнение будет продолжено после END. Это означает, что вы не можете использовать RETURN для завершения выполнения после тестирования какого-либо условия, потому что вы всегда будете в блоке IF END.
cdonner
3

Это было мое решение:

...

BEGIN
    raiserror('Invalid database', 15, 10)
    rollback transaction
    return
END
Каспер Леон Нильсен
источник
3

Вы можете использовать GOTO заявление. Попробуй это. Это полное использование для вас.

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

FinalStatement:
     Select @CoumnName from TableName
Вишал Кири
источник
GOTO считается плохой практикой кодирования, рекомендуется использовать «TRY..CATCH», так как он был введен с SQL Server 2008, а затем в 2012 году THROW.
Эдди Кумар,
1

Спасибо за ответ!

raiserror()работает нормально, но вы не должны забывать об этом, returnиначе скрипт продолжит работу без ошибок! (hense raiserror не является «throwerror» ;-)) и, конечно, делает откат при необходимости!

raiserror() Приятно сказать человеку, который выполняет сценарий, что что-то пошло не так.


источник
1

Если вы просто выполняете сценарий в Management Studio и хотите остановить выполнение или транзакцию отката (если она используется) при первой ошибке, то я считаю, что лучший способ - использовать блок try catch (начиная с SQL 2005). Это хорошо работает в Management studio, если вы выполняете файл сценария. Хранимая процедура всегда может использовать это.

Бхаргав Шах
источник
1
Что ваш ответ добавляет к принятому ответу с более чем 60 ответами? Вы читали его? Прочтите этот вопрос metaSO и Jon Skeet: Coding Blog о том, как дать правильный ответ.
Ярослав
0

Раньше мы использовали следующее ... работали лучше всего:

RAISERROR ('Error! Connection dead', 20, 127) WITH LOG
подветренный
источник
0

Заключите его в блок try catch, тогда выполнение будет передано в 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
источник