352

Is there a way to immediately stop execution of a SQL script in SQL server, like a "break" or "exit" command?

I have a script that does some validation and lookups before it starts doing inserts, and I want it to stop if any of the validations or lookups fail.

Blorgbeard
  • 93,378
  • 43
  • 217
  • 263
Andy White
  • 81,400
  • 46
  • 171
  • 205

21 Answers21

395

The raiserror method

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

This will terminate the connection, thereby stopping the rest of the script from running.

Note that both severity level 20 or higher and the WITH LOG option are necessary for it to work this way.

This even works with GO statements, eg.

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

Will give you the output:

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.

Notice that 'ho' is not printed.

CAVEATS:

  • This only works if you are logged in as admin ('sysadmin' role), and also leaves you with no database connection.
  • If you are NOT logged in as admin, the RAISEERROR() call itself will fail and the script will continue executing.
  • When invoked with sqlcmd.exe, exit code 2745 will be reported.

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

The noexec method

Another method that works with GO statements is set noexec on. This causes the rest of the script to be skipped over. It does not terminate the connection, but you need to turn noexec off again before any commands will execute.

Example:

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
  • 93,378
  • 43
  • 217
  • 263
  • Indeed this is the only method which works with multiple GO statements, which I have to use often in my database update scripts. Thanks! – AronVanAmmers Aug 20 '09 at 10:16
  • 14
    That's awesome! It's a bit of a "big stick" approach, but there are times when you really need it. Note that it requires both severity 20 (or higher) and "WITH LOG". – Rob Garrison Sep 17 '09 at 15:41
  • 6
    Note that with noexec method the rest of the script is still interpreted, so you will still get compile-time errors, such as column does not exist. If you want to conditionally deal with known schema changes involving missing columns by skipping over some code, the only way I know to do it is to use :r in sqlcommand mode to reference external files. – David Eison Mar 31 '12 at 15:48
  • For automatically generated change scripts (VS Database project --> Deploy), NOEXEC is a lifesaver – StingyJack Jul 25 '12 at 13:22
  • 22
    The noexec thing is great. Thanks a lot! – Gaspa79 Apr 08 '13 at 20:03
  • 2
    "This will terminate the connection" -- it seems that it doesn't, at least that's what I'm seeing. – jcollum Oct 09 '14 at 22:05
  • 2
    @jcollum are you using severity 20-25? [The docs](http://msdn.microsoft.com/en-us/library/ms178592.aspx) say "Severity levels from 20 through 25 are considered fatal. If a fatal severity level is encountered, the client connection is terminated after receiving the message, and the error is logged in the error and application logs." – Blorgbeard Oct 14 '14 at 18:06
  • "the client connection is terminated" that's the point -- I want script execution to completely stop – jcollum Oct 14 '14 at 19:08
  • @jcollum I found your recent question about this. It looks like it's not the `raiserror` that's the problem. The `catch` isn't triggering, presumably because the `use` is interpreted at compile time. – Blorgbeard Oct 14 '14 at 19:26
  • beware the raise error solution - it seems that some exceptions dont count as proper exceptions - you need to set a certain level of severity (20+) which you may not have permission to do! – JonnyRaa Jan 08 '15 at 16:18
  • 1
    actually turns out the magic number you need for this to work is 11-18, 10 isn't an error/exception really and 18 is the highest number you can reliably set – JonnyRaa Jan 08 '15 at 16:36
  • 6
    I was trying this method and not getting right result when I realized... There is only one E in in raiserror... – bobkingof12vs Jan 29 '16 at 15:13
  • 3
    If you want to use this in a script that will be run as a non-sysadmin user, make sure you set severity 18 or lower. The script will continue execution though. – Aidan Oct 18 '16 at 23:20
  • NoExec On is delightfully elegant If the above comment about syntax/schema validation does not prevent its use in your case. I can't imagine using the raiserror ,20, because it's conditional. – Pecos Bill Dec 26 '17 at 16:16
  • 1
    I have no control over user rights, and my admin thinks he gave enough admin rights ( on an azure database). Still, SQL Server denies me the ability to use >18 severity + with log (I am no sysadmin). The version with SQLCMD `:on error exit` is working fine, though : https://stackoverflow.com/a/2590364/479251 – Pac0 Nov 05 '18 at 10:39
  • I can confirm that on my local version of SQL 2017 Developer Edition that `raiserror('Oh no a fatal error', 20, -1) with log` does indeed terminate the connection (which is appropriate and helpful in my use case). – Jeff Mergler Nov 07 '18 at 18:51
  • 1
    Note that the sql file is still parsed, and any errors with missing columns will give errors. You can use parseonly to stop the parsing of the script. This will work the same way as noexec. – RoelAdriaans May 03 '19 at 05:45
  • I do not understand. I followed the instructions. I entered the following SQL after each GO. `IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END;` But the execution never stopped, and I ended up with three "Rolling back Transaction"s errors raised. Any ideas? – user1161391 Jul 19 '19 at 21:57
  • I got "Only System Administrator can specify WITH LOG option for RAISERROR command"... :-( – Valid Sep 15 '20 at 18:48
  • The command `set noexec on` is perfect for debugging! using `raiserror` always drives me nuts because my script always seems to stop BEFORE processing the lines right above the `raiserror` command. noexec does not have this issue. – Arvo Bowen Sep 22 '20 at 20:41
  • While this works great from SSMS, is does not seem to work inside `IF` blocks within Agent. I get an `Incorrect syntax near the keyword 'end'` message. – RonJohn Oct 04 '20 at 07:29
198

Just use a RETURN (it will work both inside and outside a stored procedure).

Gordon Bell
  • 12,283
  • 3
  • 41
  • 63
  • 2
    For some reason, I was thinking that return didn't work in scripts, but I just tried it, and it does! Thanks – Andy White Mar 18 '09 at 18:17
  • 4
    In a script, you can't do a RETURN with a value like you can in a stored procedure, but you can do a RETURN. – Rob Garrison Sep 17 '09 at 16:08
  • 63
    No it only terminates *until the next GO* The next batch (after GO) will run as usual – mortb Mar 05 '13 at 15:46
  • But you can SET CONTEXT_INFO at the end of a batch and check if it is what is expected at the start of the next batch as described in jaraics response. – pete Jun 10 '15 at 10:34
  • 2
    dangerous to assume as it will continue after then next GO. – Justin Jun 02 '16 at 04:21
  • 3
    GO is a script terminator or delimiter; it's not SQL code. GO is just an instruction to the client you're using to send command to the database engine that a new script is starting after the GO delimiter. – Reversed Engineer Oct 24 '17 at 08:38
  • This is an older question but still relevant. **Using RETURN with an IF/ELSE statement also allows some user info or logging to take place:** IF (SELECT @@SERVERNAME) LIKE '%WrongServerName%' BEGIN SELECT 'Please find someone else to help you with this' AS Cmon_Man RETURN END ELSE SELECT 'Continuing on...' AS Very_Good – DBADon Nov 02 '17 at 16:03
  • 1
    This answer doesn't help with the question at all, which is "Is there a way to immediately stop execution of a SQL script". – Steve Smith Apr 13 '18 at 12:58
58

If you can use SQLCMD mode, then the incantation

:on error exit

(INCLUDING the colon) will cause RAISERROR to actually stop the script. E.g.,

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

will output:

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

and the batch will stop. If SQLCMD mode isn't turned on, you'll get parse error about the colon. Unfortuantely, it's not completely bulletproof as if the script is run without being in SQLCMD mode, SQL Managment Studio breezes right past even parse time errors! Still, if you're running them from the command line, this is fine.

Sglasses
  • 581
  • 4
  • 2
21

I would not use RAISERROR- SQL has IF statements that can be used for this purpose. Do your validation and lookups and set local variables, then use the value of the variables in IF statements to make the inserts conditional.

You wouldn't need to check a variable result of every validation test. You could usually do this with only one flag variable to confirm all conditions passed:

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

Even if your validation is more complex, you should only need a few flag variables to include in your final check(s).

fizzled
  • 668
  • 5
  • 6
Dave Swersky
  • 33,678
  • 9
  • 73
  • 115
  • Yeah, I'm using IFs in other parts of the script, but I don't want to have to check every local variable before I try to do an insert. I'd rather just have the whole script stop, and force the user to check the inputs. (This is just a quick and dirty script) – Andy White Mar 18 '09 at 17:14
  • 4
    I'm not quite sure why this answer has been marked down becuase it is technically correct, just not what the poster "wants" to do. – John Sansom Mar 18 '09 at 17:17
  • Is it possible to have multiple blocks within Begin..End? Meaning STATEMENT; GO; STATEMENT; GO; etc etc? I'm getting errors and I guess that might be the reason. – Joel Peltonen May 06 '14 at 12:48
  • 3
    This is far more reliable than `RAISERROR`, especially if you don't know who is going to be running the scripts and with what privileges. – Cypher Oct 15 '15 at 16:10
  • @John Sansom: The only problem I see here is that the IF statement does not work if you are attempting to branch over a GO statement. This is a big problem if your scripts rely on the GO statements (e.g. DDL statements). Here is an example that works _without_ the first go statement: `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 Feb 21 '18 at 13:55
17

In SQL 2012+, you can use THROW.

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

From MSDN:

Raises an exception and transfers execution to a CATCH block of a TRY…CATCH construct ... If a TRY…CATCH construct is not available, the session is ended. The line number and procedure where the exception is raised are set. The severity is set to 16.

Jordan Parker
  • 1,108
  • 1
  • 14
  • 24
  • 1
    THROW is meant to replace RAISERROR, but you can't prevent subsequent batches in the same script file with it. – NReilingh Nov 25 '16 at 21:00
  • Correct @NReilingh. That's where Blorgbeard's answer is really the only solution. It does require sysadmin, though (severity level 20), and it is fairly heavy-handed if there aren't multiple batches in the script. – Jordan Parker Nov 27 '16 at 17:27
  • 2
    set xact abort on if you want to cancel the current transcation as well. – nurettin Jan 16 '18 at 13:42
14

I extended the noexec on/off solution successfully with a transaction to run the script in an all or nothing manner.

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

Apparently the compiler "understands" the @finished variable in the IF, even if there was an error and the execution was disabled. However, the value is set to 1 only if the execution was not disabled. Hence I can nicely commit or rollback the transaction accordingly.

Tz_
  • 2,919
  • 16
  • 13
  • I do not understand. I followed the instructions. I entered the following SQL after each GO. `IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END;` But the execution never stopped, and I ended up with three "Rolling back Transaction"s errors raised. Any ideas? – user1161391 Jul 19 '19 at 22:07
12

you could wrap your SQL statement in a WHILE loop and use BREAK if needed

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
  • 102,662
  • 42
  • 131
  • 170
  • 5
    I kind of like the looks of this, it seems a little nicer than raise error. Definitely don't want to forget the break at the end! – Andy White Mar 18 '09 at 18:10
  • 2
    You could also use a variable and immediately set it at the top of the loop to avoid the "split". `DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; END` More verbose, but heck, it's TSQL anyway ;-) –  Mar 10 '12 at 00:15
  • This is how some people perform goto, but it is more confusing to follow than goto. – nurettin Jan 16 '18 at 13:44
  • This approach protects from an unexpected occasional GO. Appreciative. – it3xl Jan 18 '18 at 14:36
11

Further refinig Sglasses method, the above lines force the use of SQLCMD mode, and either treminates the scirpt if not using SQLCMD mode or uses :on error exit to exit on any error
CONTEXT_INFO is used to keep track of the state.

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
  • 3,979
  • 3
  • 28
  • 35
  • 2
    This is the only way I found to work around the SSMS lunacy of being unable to abort the script. But I added 'SET NOEXEC OFF' at the beginning, and 'SET NOEXEC ON' if not in SQLCMD mode, otherwise the actual script will keep going unless you raise an error at level 20 with log. – Mark Sowul Feb 10 '12 at 19:49
  • !!!!!!!!!!!!! THANK YOU !!!!!!!!!!!! Most answers I have seen disregard script with multiple batches. And they ignore dual usage in SSMS and SQLCMD. My script is fully runable in SSMS -- but I want an F5 prevention so they don't remove an existing set of objects on accident. `SET PARSEONLY ON` worked well enough for that. But then you can't run with SQLCMD. I have also not seen remarks about SET NOCOUNT ON not working when anything in the same batch doesn't compile -- that through me sideways for a while. I added a tiny bit to this in an answer below. – Thomas Oatman Apr 28 '21 at 22:43
11

You can alter the flow of execution using GOTO statements:

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

/* our code */

EndScript:
Charlie
  • 1,185
  • 14
  • 17
  • 2
    using goto is an acceptable way to handle exception. Reduces the amount of variables and nesting and does not cause a disconnect. It's probably preferable to the archaic exception handling that SQL Server scripting allows. – Antonio Drusin Nov 20 '19 at 14:48
  • 1
    Just like ALL of the other suggestions here, this doesn't work if "our code" contains a "GO" statement. – Mike Gledhill Jun 10 '20 at 12:08
  • Oddly, this still parsed the script; I got "Incorrect syntax near the keyword 'with'" errors but I was able to prevent the script from running without sysadmin (after removing all the 'GO' commands). Thanks. – Valid Sep 15 '20 at 19:30
8

Is this a stored procedure? If so, I think you could just do a Return, such as "Return NULL";

mtazva
  • 997
  • 8
  • 13
6

I would suggest that you wrap your appropriate code block in a try catch block. You can then use the Raiserror event with a severity of 11 in order to break to the catch block if you wish. If you just want to raiserrors but continue execution within the try block then use a lower severity.

Make sense?

Cheers, John

[Edited to include BOL Reference]

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

John Sansom
  • 39,457
  • 9
  • 68
  • 81
6

I use RETURN here all the time, works in script or Stored Procedure

Make sure you ROLLBACK the transaction if you are in one, otherwise RETURN immediately will result in an open uncommitted transaction

jerryhung
  • 973
  • 5
  • 10
  • 6
    Doesn't work with a script containing multiple batches (GO statements) - see my answer for how to do that. – Blorgbeard Apr 29 '09 at 23:45
  • 1
    RETURN just exits the current block of statements. If you are in an IF END block, execution will continue after the END. This means you cannot use RETURN to end execution after testing for some condition, because you will always be in IF END block. – cdonner May 15 '12 at 14:58
4

you can use RAISERROR.

Mladen Prajdic
  • 15,018
  • 2
  • 39
  • 48
  • 3
    This makes no sense to me- raising an avoidable error (assuming we're talking about referential validation here) is a horrible way to do this if validation is possible before the inserts take place. – Dave Swersky Mar 18 '09 at 17:14
  • 2
    raiserror can be used as an informational message with a low severity setting. – Mladen Prajdic Mar 18 '09 at 17:42
  • 2
    The script will continue unless certain conditions stated in the accepted answer are met. – Eric J. May 05 '13 at 22:11
4

None of these works with 'GO' statements. In this code, regardless of whether the severity is 10 or 11, you get the final PRINT statement.

Test Script:

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

Results:

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

The only way to make this work is to write the script without GO statements. Sometimes that's easy. Sometimes it's quite difficult. (Use something like IF @error <> 0 BEGIN ....)

Cody Gray
  • 222,280
  • 47
  • 466
  • 543
Rob Garrison
  • 6,564
  • 4
  • 20
  • 23
3

This was my solution:

...

BEGIN
    raiserror('Invalid database', 15, 10)
    rollback transaction
    return
END
Casper Leon Nielsen
  • 2,289
  • 1
  • 25
  • 36
3

You can use GOTO statement. Try this. This is use full for you.

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

FinalStatement:
     Select @CoumnName from TableName
Vishal Kiri
  • 1,020
  • 1
  • 10
  • 23
  • GOTO is supposed to be a bad coding practice, use of "TRY..CATCH" is recommended, as it was introduced since SQL Server 2008, followed by THROW in 2012. – Eddie Kumar Nov 15 '18 at 16:08
1

If you are simply executing a script in Management Studio, and want to stop execution or rollback transaction (if used) on first error, then the best way I reckon is to use try catch block (SQL 2005 onward). This works well in Management studio if you are executing a script file. Stored proc can always use this as well.

  • 1
    What does your answer adds to the accepted answer with 60+ upvotes? Have you read it? Check this [metaSO question](http://meta.stackexchange.com/questions/7656/how-do-i-write-a-good-answer-to-a-question) and [Jon Skeet: Coding Blog](http://msmvps.com/blogs/jon_skeet/archive/2009/02/17/answering-technical-questions-helpfully.aspx) on how to give a correct answer. – Yaroslav Oct 11 '12 at 13:38
1

Enclose it in a try catch block, then the execution will be transfered to 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
  • 545
  • 1
  • 4
  • 14
1

Thx for the answer!

raiserror() works fine but you shouldn't forget the return statement otherwise the script continues without error! (hense the raiserror isn't a "throwerror" ;-)) and of course doing a rollback if necessary!

raiserror() is nice to tell the person who executes the script that something went wrong.

0

Back in the day we used the following...worked best:

RAISERROR ('Error! Connection dead', 20, 127) WITH LOG
Lee
  • 171
  • 7
0

Many thanks to all the other people here and other posts I have read. But nothing was meeting all of my needs until @jaraics answered.

Most answers I have seen disregard scripts with multiple batches. And they ignore dual usage in SSMS and SQLCMD. My script is fully runable in SSMS -- but I want F5 prevention so they don't remove an existing set of objects on accident.

SET PARSEONLY ON worked well enough to prevent unwanted F5. But then you can't run with SQLCMD.

Another thing that slowed me down for a while is how a Batch will skip any further commands when there is an error - so my SET NOCOUNT ON was being skipped and thus the script still ran.

Anyway, I modified jaraics' answer just a bit: (in this case, I also need a database to be active from commandline)

-----------------------------------------------------------------------
-- Prevent accidental F5
-- Options:
--     1) Highlight everything below here to run
--     2) Disable this safety guard
--     3) or use SQLCMD
-----------------------------------------------------------------------
set NOEXEC OFF                             -- Reset in case it got stuck ON
set CONTEXT_INFO  0x1                      -- A 'variable' that can pass batch boundaries
GO                                         -- important !
if $(SQLCMDDBNAME) is not null
    set CONTEXT_INFO 0x2                   -- If above line worked, we're in SQLCMD mode
GO                                         -- important !
if CONTEXT_INFO()<>0x2 
begin
    select 'F5 Pressed accidentally.'
    SET NOEXEC ON                          -- skip rest of script
END
GO                                         -- important !
-----------------------------------------------------------------------

< rest of script . . . . . >


GO
SET NOEXEC OFF
print 'DONE'