SQL Server-停止或中断SQL脚本的执行


325

有没有一种方法可以立即停止在SQL Server中执行SQL脚本,例如“ break”或“ exit”命令?

我有一个脚本,该脚本在开始执行插入操作之前会进行一些验证和查找,并且如果任何验证或查找失败,我希望它停止。

Answers:


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”未打印。

注意事项:

  • 仅当您以admin('sysadmin'角色)身份登录时才有效,并且也没有数据库连接。
  • 如果您未以admin身份登录,则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(或更高)和“ WITH LOG”。
罗布·加里森

5
请注意,使用noexec方法时,脚本的其余部分仍会被解释,因此您仍然会遇到编译时错误,例如不存在column。如果要跳过某些代码来有条件地处理涉及缺少列的已知模式更改,我知道的唯一方法是在sqlcommand模式下使用:r引用外部文件。
大卫·艾森

20
noexec很棒。非常感谢!
Gaspa79 2013年

2
“这将终止连接” –似乎没有,至少那是我所看到的。
jcollum 2014年

6
我尝试这种方法时并没有获得正确的结果,当我意识到...
rarrrror中

187

只需使用RETURN(它将在存储过程的内部和外部都起作用)。


2
由于某种原因,我当时认为return在脚本中不起作用,但我只是尝试了,它确实可以!谢谢
安迪·怀特

4
在脚本中,不能像在存储过程中那样使用值进行RETURN,但是可以执行RETURN。
罗布·加里森

53
不,它只会终止直到下一个GO。下一批(GO之后)将照常运行
mortb

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甚至在解析时间错误时都轻而易举!不过,如果您是从命令行运行它们,那很好。


4
很好的评论,谢谢。我将添加在SSMS SQLCmd模式下在“查询”菜单下切换。
大卫·彼得斯

这很有用-表示您在运行时不需要-b选项
JonnyRaa 2015年

2
然后是咒语...但是我该如何施放魔法小姐?
JJS

1
完善。不需要sysadmin超级用户权限
Pac0 '18

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,但是在尝试执行插入操作之前,我不想检查每个局部变量。我宁愿停止整个脚本,并强迫用户检查输入。(这只是一个快速而肮脏的脚本)
安迪·怀特2009年

4
我不太确定为什么要将此答案标记为下,因为它在技术上是正确的,而不是发帖人“想要”做的。
约翰·桑索姆

是否可以在Begin..End中包含多个块?含义声明;走; 声明; 走; 等等等等?我遇到错误,我想这可能是原因。
内诺普(Nenotlep)

3
这比可靠得多RAISERROR,尤其是在您不知道谁将运行脚本以及具有哪些特权的情况下。
Cypher

@John Sansom:我在这里看到的唯一问题是,如果您尝试分支到GO语句,则IF语句不起作用。如果您的脚本依赖于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
James Jensen

16

在SQL 2012+,您可以使用THROW

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

从MSDN:

引发异常并将执行转移到TRY ... CATCH构造的CATCH块中。如果TRY ... CATCH构造不可用,则会话结束。设置引发异常的行号和过程。严重性设置为16。


1
THROW旨在代替RAISERROR,但您不​​能阻止它与它一起在同一脚本文件中进行后续批处理。
NReilingh '16

正确@NReilingh。那是Blorgbeard的答案实际上是唯一的解决方案。不过,它确实需要sysadmin(严重性级别20),并且如果脚本中没有多个批处理,则它的处理非常繁琐。
Jordan Parker

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

显然,即使有错误并且执行被禁用,编译器也会“理解” IF中的@finished变量。但是,仅当未禁用执行时,该值才设置为1。因此,我可以很好地相应地提交或回滚事务。


我不明白。我按照指示进行。我在每个GO之后输入了以下SQL。 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更冗长,但

这是某些人执行goto的方式,但是跟goto相比,它更令人困惑。
nurettin

这种方法可以防止意外的GO。感激。
it3xl

10

您可以使用GOTO语句更改执行流程:

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

/* our code */

EndScript:

2
使用goto是处理异常的可接受方法。减少变量和嵌套的数量,并且不会导致断开连接。它可能比SQL Server脚本允许的过时异常处理更好。
Antonio Drusin

就像这里的所有其他建议一样,如果“我们的代码”包含“ GO”语句,则此方法将无效。
Mike Gledhill

9

进一步使用refinig Sglasses方法时,以上几行强制使用SQLCMD模式,如果不使用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”,如果不在SQLCMD模式下,则添加了“ SET NOEXEC ON”,否则实际脚本将继续运行,除非您在日志级别20出现错误。
Mark Sowul

8

这是存储过程吗?如果是这样,我想你可以做一个Return,例如“ Return NULL”;


感谢您的回答,这是一个很好的了解,但是在这种情况下,它不是存储的proc,而只是一个脚本文件
Andy White 2009年

1
@戈登并非总是如此(我在这里搜索)。查看其他答案(GO将其绊倒,是一回事)
Mark Sowul 2014年

6

我建议您将适当的代码块包装在try catch块中。然后,您可以使用严重性为11的Raiserror事件,以便根据需要中断到catch块。如果您只想提高筹码,但继续在try块中执行,则使用较低的严重性。

合理?

干杯,约翰

[经编辑以包含BOL参考]

http://msdn.microsoft.com/zh-CN/library/ms175976(SQL.90).aspx


我从未见过SQL的try-catch-您介意发布一个简短的示例说明您的意思吗?
安迪·怀特

2
它是2005年的新功能。开始尝试{sql_statement | statement_block}尝试开始捕获{sql_statement | statement_block}结束捕获[; ]
萨姆

@Andy:添加了参考,包括示例。
John Sansom,2009年

2
TRY-CATCH块不允许itelf进入GO。
13年

4

您可以使用RAISERROR


3
如果提出可以在插入之前进行验证的方法,那么提出一个可以避免的错误(假设我们在这里指的是参照验证)是没有道理的。
Dave Swersky 09年

2
raiserror可以用作严重性设置较低的参考消息。
Mladen Prajdic 09年

2
除非满足接受的答案中所述的某些条件,否则脚本将继续。
Eric J.

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 2012年


3

您可以使用GOTO语句。尝试这个。这对您已经用光了。

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

FinalStatement:
     Select @CoumnName from TableName

GOTO被认为是一种不良的编码习惯,建议使用“ TRY..CATCH”,因为它是从SQL Server 2008开始引入的,随后是2012
Eddie Kumar

1

谢谢你的答案!

raiserror()可以正常工作,但您不应忘记该return语句,否则脚本将继续运行而不会出错!(因此,raiserror不是“ throwerror” ;-)),当然,如果需要,可以进行回滚!

raiserror() 很高兴告诉执行脚本的人出了点问题。


1

如果您只是在Management Studio中执行脚本,并且想在出现第一个错误时停止执行或回滚事务(如果使用),那么我认为最好的方法是使用try catch块(从SQL 2005开始)。如果您正在执行脚本文件,这在Management Studio中效果很好。存储的proc也可以始终使用它。


1
您的答案加上60多个投票后又增加了什么?你看了吗?查看此metaSO问题和有关如何给出正确答案的Jon Skeet:编码博客
Yaroslav

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;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.