Behavior of WITH NOWAIT option with RAISERROR in SQL Server

In some situations you would like to use WITH NOWAIT option in SQL Server RAISERROR statement, but somehow you usually have to wait until the procedure is complete before seeing messages. You search through MSDN links but end up reading several different articles. Here I am trying to put all the things under one roof to make you understand how it works and what different options you could make use of. RAISERROR is used to return messages back to applications using the same format as a system error or warning message generated by the SQL Server Database Engine. NOWAIT is a custom option for the error which sends messages immediately to the client. Ideal behavior would be to produce stored procedure and RAISERROR output in a way that keeps you informed of how the code is progressing. So, the motive here is to get an insight of the code's progress using WITH NOWAIT option.

Please note that RAISERROR doesn’t require an error to be raised explicitly. If the severity level passed to RAISERROR as a parameter is 0 - 10 SQL Server treats the RAISERROR as a plain message and will not show it as an error. Execution continues with the next statement, even if there is a TRY/CATCH block or if SET XACT_ABORT is ON. You could use one of the RAISERROR severities of 0 to 10 and the WITH NOWAIT clause for a statement that sends output immediately. Following example will show you the same:

RAISERROR ('This is a RAISERROR condition', 0, 1) WITH NOWAIT

Once you do this, you would wonder where the output is getting generated. In SQL Server SSMS and Query Analyzer, when you run a query the results are sent to a "Grid" window and the "Messages" window is hidden unless there are no results. There are two ways to address this issue:

1. One option is to allow results to go to the Grid and click on the Messages window.

2. Another option is to use the SSMS menu bar àWindowàNext Pane (F6) or Shift+F6 in Query Analyzer.

3. You could also send the results to text using menu or CTRL+T. Once you've sent Results to Text or CTRL+T.

Now run the following lines of code:

PRINT '1. This message does not display immediately'

WAITFOR DELAY '00:00:05'

RAISERROR('2. This is raised with severity 0-10.', 1, 1) WITH NOWAIT

RAISERROR ('3. This message is displayed immediately', 0, 1) WITH NOWAIT

WAITFOR DELAY '00:00:05'

PRINT '4. It''s over now'

Output

------------

1. This message does not display immediately

2. This is raised with severity 0-10.

Msg 50000, Level 1, State 1

3. This message is displayed immediately

4. It's over now

Now, do the following:

PRINT '1. This message does not display immediately'

WAITFOR DELAY '00:00:05'

RAISERROR('2. This is raised with severity >=11.', 12, 1) WITH NOWAIT

RAISERROR ('3. This message is displayed immediately', 0, 1) WITH NOWAIT

WAITFOR DELAY '00:00:05'

PRINT '4. It''s over now'

Output

------------

1. This message does not display immediately

Msg 50000, Level 12, State 1, Line 4

2. This is raised with severity >=11.

3. This message is displayed immediately

4. It's over now

Can you find the difference between the two executions of the above batch? Well, the difference is the change in the Severity of the RAISERROR statements. In the first RAISERROR statement under the first batch of statements I have given the severity as 1 whereas in the second one the severity is 12. As I said before if the severity level given to RAISERROR is 0 through 10 SQL Server treats the RAISERROR as a plain message and not an error at all. In the first output you will get the results in plain text (black color) and you will get the text given in the RAISERROR statement first and then the Msg 50000. In the second output NOWAIT has no effect on this behavior because of Severity 12 and hence you will get Msg 50000 first and then the message text. Also, the entire output of 2nd statement will be in the red color as if SQL encountered an error.

Why Msg 50000? This is because when msg_id is not specified, RAISERROR raises an error message with an error number of 50000.

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. Another thing to remember here is that when RAISERROR is run with a severity of 11 or higher in a TRY block, it transfers control to the associated CATCH block. The error is returned to the caller if RAISERROR is run:

1. Outside the scope of any TRY block.

2. With a severity of 10 or lower in a TRY block.

3. With a severity of 20 or higher that terminates the database connection.

If you specify a severity of 10 or lower, the RAISERROR will return a message from a TRY block without invoking the CATCH block.

Consider following lines of code:

BEGIN TRY

    -- RAISERROR with severity 0-10 will not cause execution to jump to the CATCH block.

      PRINT '1. Execution is in TRY Block with severity 0-10'

      WAITFOR DELAY '00:00:05'

     RAISERROR ('2. Error raised in TRY block.', 5, 1) with NOWAIT

      PRINT '3. Control did not go to CATCH Block'

      WAITFOR DELAY '00:00:05'

      PRINT '4. It''s over now'

END TRY

BEGIN CATCH

    -- Use RAISERROR inside the CATCH block to return error information about the original error that caused

    -- execution to jump to the CATCH block.

      WAITFOR DELAY '00:00:05'

      RAISERROR ('5. Error raised in Catch block.',5, 1)

END CATCH;

Output

--------------

1. Execution is in TRY Block with severity 0-10

2. Error raised in TRY block.

Msg 50000, Level 5, State 1

3. Control did not go to CATCH Block

4. It's over now

In the above output you will find that since the severity is between 0-10 the control did not passed to CATCH block and the effect of NOWAIT remain as it is on RAISERROR.

Now consider following lines of code:

BEGIN TRY

    -- RAISERROR with severity 11-19 will cause execution to

    -- jump to the CATCH block.

   

      PRINT '1. Execution is in TRY Block with severity >=11'

      WAITFOR DELAY '00:00:05'

      RAISERROR ('2. Error raised in TRY block.', 16, 1) with NOWAIT

      WAITFOR DELAY '00:00:05'

      PRINT '3. It''s over now'

END TRY

BEGIN CATCH

    -- Use RAISERROR inside the CATCH block to return error information about the original error that caused

    -- execution to jump to the CATCH block.

   

    RAISERROR ('4. Error raised in Catch block', 16, 1) WITH NOWAIT

      WAITFOR DELAY '00:00:05'

      PRINT '3. It''s over now'

END CATCH;

Output

---------------

1. Execution is in TRY Block with severity >=11

Msg 50000, Level 16, State 1, Line 18

4. Error raised in Catch block

3. It's over now

Here, since the severity is more than >=11 the control immediately passed from RAISERROR to CATCH block and execution was resumed inside CATCH block which eventually raises the error. Since the control immediately passed to the CATCH block you will not see any effect of NOWAIT at all with RAISERROR defined in TRY block. However, NOWAIT will have effect with RAISERROR defined in CATCH block.

This behavior is same on all editions of SQL Server so you don't have change the code again and again for different versions :)

Sumit Sarabhai
SE, Microsoft Sql Server

Reviewed By

Mukesh Nanda
TL, Microsoft Sql Server