98

I want to rethrow the same exception in SQL Server that has just occurred in my try block. I am able to throw same message but I want to throw same error.

BEGIN TRANSACTION
    BEGIN TRY
        INSERT INTO Tags.tblDomain (DomainName, SubDomainId, DomainCode, Description)
            VALUES(@DomainName, @SubDomainId, @DomainCode, @Description)
        COMMIT TRANSACTION
    END TRY
    
    BEGIN CATCH
        declare @severity int; 
        declare @state int;

        select @severity=error_severity(), @state=error_state();

        RAISERROR(@@Error,@ErrorSeverity,@state);
        ROLLBACK TRANSACTION
    END CATCH

RAISERROR(@@Error, @ErrorSeverity, @state);

This line will show error, but I want functionality something like that. This raises error with error number 50000, but I want the error number to be thrown that I am passing @@error,

I want to capture this error no at the frontend.

i.e.

catch (SqlException ex)
{
    if ex.number==2627
    MessageBox.show("Duplicate value cannot be inserted");
}

I want this functionality. which can't be achieved using raiseerror. I don't want to give custom error message at back end.

RAISEERROR should return below mentioned error when I pass ErrorNo to be thrown in catch

Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,

Line 14 Violation of UNIQUE KEY constraint 'UK_DomainCode'. Cannot insert duplicate key in object 'Tags.tblDomain'. The statement has been terminated.

EDIT:

What can be the drawback of not using try catch block if I want exception to be handled at frontend considering stored procedure contains multiple queries that need to be executed?

Dale K
  • 25,246
  • 15
  • 42
  • 71
Shantanu Gupta
  • 20,688
  • 54
  • 182
  • 286

10 Answers10

156

SQL 2012 introduces the throw statement:

http://msdn.microsoft.com/en-us/library/ee677615.aspx

If the THROW statement is specified without parameters, it must appear inside a CATCH block. This causes the caught exception to be raised.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH
Tracker1
  • 19,103
  • 12
  • 80
  • 106
Michael
  • 11,571
  • 4
  • 63
  • 61
  • 3
    Beware, looks like this solution works only from sql server 2012 and above: https://msdn.microsoft.com/en-us/library/ee677615.aspx – Adi Jul 24 '15 at 13:16
  • 3
    @BogdanBogdanov So that you can log the error, possibly handle some situations, but if you can't then you want to rethrow the error so any higher up try/catch can then have a chance to handle it – Robert McKee Mar 03 '16 at 18:05
  • Yes, @Robert McKee. I figure out that. Sorry, that forgot to clear this comment. – Bogdan Bogdanov Mar 06 '16 at 19:07
  • 8
    That semicolon on the `ROLLBACK` line is important! Without it you may get a `SQLException: Cannot roll back THROW`. – idontevenseethecode Apr 03 '19 at 18:21
135

Here is a fully functional clean code sample to rollback a series of statements if an error occurs and reports the error message.

begin try
    begin transaction;

    ...

    commit transaction;
end try
begin catch
    if @@trancount > 0 rollback transaction;
    throw;
end catch

Before SQL 2012

begin try
    begin transaction;
    
    ...
    
    commit transaction;
end try
begin catch
    declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
    select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
    if @@trancount > 0 rollback transaction;
    raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
end catch
Ben Gripka
  • 16,012
  • 6
  • 45
  • 41
  • 10
    I was using this in the middle of a stored procedure, and found that it would continue to execute after `raiserror`, which is different than how c# exits after a `throw`. So I added a `return` inside the `catch` because I wanted to match that behavior. – Brian J Apr 09 '14 at 15:40
  • @BogdanBogdanov I rolled back your edit because the point of this code is to be minimal and not detract from the real code entered in place of the ... – Ben Gripka Feb 09 '16 at 16:01
  • Ok, no problem, @Ben Gripka. I try to make it more readable on the screen. Thank you for pointing the reason to make rollback. – Bogdan Bogdanov Feb 09 '16 at 16:12
  • 1
    @BrianJ: normally, whether the execution stops or not depends on the severity of the original error. If the severity is >= 11 then the execution should stop. It is really weird, because the raiserror inside the catch block with a severity >=11 doesn't stop the execution anymore. Your observation is very good and it shows how brain-dead is sql server, at least 2008r2. The newer versions seem better. – boggy Feb 22 '17 at 23:53
  • 1
    @costa See [`RAISERROR()`’s docs](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/raiserror-transact-sql?view=sql-server-2017#remarks). Severity of ≥11 only jumps to a `CATCH` block if it’s inside of a `TRY` block. So you **must** have `BEGIN TRY…END CATCH` around code if you want your `RAISERROR()` to affect flow control. – binki Jul 27 '19 at 20:22
9

Rethrowing inside the CATCH block (pre-SQL2012 code, use THROW statement for SQL2012 and later):

DECLARE
    @ErrorMessage nvarchar(4000) = ERROR_MESSAGE(),
    @ErrorNumber int = ERROR_NUMBER(),
    @ErrorSeverity int = ERROR_SEVERITY(),
    @ErrorState int = ERROR_STATE(),
    @ErrorLine int = ERROR_LINE(),
    @ErrorProcedure nvarchar(200) = ISNULL(ERROR_PROCEDURE(), '-');
SELECT @ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 'Message: ' + @ErrorMessage;
RAISERROR (@ErrorMessage, @ErrorSeverity, 1, @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine)
SeriousM
  • 3,374
  • 28
  • 33
nzeemin
  • 901
  • 8
  • 17
5

I think your choices are:

  • Dont catch the error (let it bubble up)
  • Raise a custom one

At some point, SQL will probably introduce a reraise command, or the ability to catch only certain errors. But for now, use a workaround. Sorry.

Rob Farley
  • 15,625
  • 5
  • 44
  • 58
  • 8
    in sql 2012 you can re-raise an exception using the new THROW keyword – sergiom Feb 27 '13 at 10:09
  • 6
    Yes. Of course, that wasn't available when this question was asked. – Rob Farley Feb 28 '13 at 21:28
  • It would be more important to catch and throw a new error than not catch it and let it 'bubble up' because you will likely require some clean-up, corrective action and closure activities to handle the exception properly. An obvious example would be to close and dispose of a cursor. Other examples may be to execute a logging procedure, or to reset some data. – Antony Booth Sep 08 '14 at 18:25
1

You can't: only the engine can throw errors less than 50000. All you can do is throw an exception that looks like it...

See my answer here please

The questioner here used client side transactions to do what he wanted which I think is a wee bit silly...

Community
  • 1
  • 1
gbn
  • 422,506
  • 82
  • 585
  • 676
0

The way to stop execution in a stored procedure after an error has occurred and bubble the error back to the calling program is to follow each statement that might throw an error with this code:

If @@ERROR > 0
Return

I was surprised myself to find out that execution in a stored procedure can continue after an error - not realizing this can lead to some hard to track down bugs.

This type of error handling parallels (pre .Net) Visual Basic 6. Looking forward to the Throw command in SQL Server 2012.

Chuck Bevitt
  • 51
  • 1
  • 1
0

Given that you haven't moved to 2012 yet, one way to implement the bubbling up of the original error code is to use the text message part of the exception you are (re)throwing from the catch block. Remember that it can contain some structure, for example, XML text for your caller code to parse in its catch block.

Yuri Makassiouk
  • 425
  • 3
  • 16
0

Ok, this is a workaround...:-)

DECLARE @Error_Number INT
BEGIN TRANSACTION 
    BEGIN TRY
    INSERT INTO Test(Id, Name) VALUES (newID(),'Ashish') 
    /* Column 'Name' has unique constraint on it*/
    END TRY
    BEGIN CATCH

            SELECT ERROR_NUMBER()
            --RAISERROR (@ErrorMessage,@Severity,@State)
            ROLLBACK TRAN
    END CATCH

If you note the catch block, It is not raising the error but returning the actual error number (and also would rollback the transaction). Now in your .NET code, instead of catching the exception, if you use ExecuteScalar(), you get the actual error number you want and show the appropriate number.

int errorNumber=(int)command.ExecuteScalar();
if(errorNumber=<SomeNumber>)
{
    MessageBox.Show("Some message");
}

Hope this helps,

EDIT :- Just a note, If you want to get the number of records affected and trying to use ExecuteNonQuery, the above solution may not work for you. Otherwise, I think It would suit what you need. Let me know.

Ashish Gupta
  • 14,869
  • 20
  • 75
  • 134
  • @Ashish Gupta: Thx for help, But i need exception to be thrown from database to the frontend, otherwise i have many options open like print error_number(), return error_number and the 1 u suggested – Shantanu Gupta Mar 20 '10 at 08:38
0

You can also create a wrapper stored procedure for the those scenarios when you want the SQL statement to be executed within the transaction and feed the error up to your code.

CREATE PROCEDURE usp_Execute_SQL_Within_Transaction
(
    @SQL nvarchar(max)
)
AS

SET NOCOUNT ON

BEGIN TRY
    BEGIN TRANSACTION
        EXEC(@SQL)
    COMMIT TRANSACTION
END TRY

BEGIN CATCH
    DECLARE @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int
    SELECT @ErrorMessage = N'Error Number: ' + CONVERT(nvarchar(5), ERROR_NUMBER()) + N'. ' + ERROR_MESSAGE() + ' Line ' + CONVERT(nvarchar(5), ERROR_LINE()), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE()
    ROLLBACK TRANSACTION
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH

GO

-- Test it
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1/0; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'EXEC usp_Another_SP'
Sergey
  • 179
  • 2
  • 4
-2

From a design point of view, what is the point of throwing exceptions with original error numbers and custom messages? To some extent it breaks the interface contract between applications and the database. If you want to catch original errors and handle them in higher code, don't handle them in the database. Then when you catch an exception you can change the message presented to the user to anything you want. I would not do it though, because it makes your database code hmm 'not right'. As others said you should define a set of your own error codes (above 50000) and throw them instead. Then you can hanle integrity issues ('Duplicate values are not allowed') separately from potential business issues - 'Zip code is invalid', 'No rows were found matching the criteria' and so on.

Piotr Rodak
  • 1,931
  • 1
  • 13
  • 8
  • 9
    What is the point of throwing exceptions with original error numbers and custom messages? Suppose you want to handle one or two specific (expected) errors directly in the catch block and leave the rest for the higher layers. Thus you need to be able to rethrow the exceptions you did not handle ... preferably without having to resort to reporting and handling the errors some other, special way. – Jenda Aug 28 '12 at 11:49
  • 1
    In addition to what @Jenda explained, I like to use try-catch to ensure that code execution is not continuing after an exception, much like in do in C#: `try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }`, where `throw;` will just raise the original exception, with its original context. – R. Schreurs Apr 12 '13 at 09:37
  • I catch errors and re-throw custom error messages in SQL to add details describing which line the error happened or other details (such as the data trying to be inserted) to help me track down the error later. – Russell Hankins Jul 19 '16 at 20:12