The goal of this article is to provide a simple and easy to use error handling mechanism with minimum complexity.
Yes, there is. The TRY/CATCH construct is the structured mechanism for error handling in SQL Server 2005 and later. This construct has two parts; we can try executing some statements in TRY block and handling errors in the CATCH block if they occur. Therefore, the simplest error handling structure can be like this:
SET
NOCOUNT
ON
;
BEGIN
TRY
-- Start to try executing statements
SELECT
1 / 0; /* Executing statements */
END
-- End of trying to execute statements
CATCH
-- Start to Handle the error if occurs
PRINT
'Error occurs!'
/* Handle the error */
-- End of Handling the error if occurred
--result
We can see this behavior with an example. As you can see after executing the following code, the statement no. 3 does not try executing, because the flow of execution will transfer to the CATCH block as soon as statement no. 2 raises an error.
'Before Error!'
-- Statement no1
1 / 0;
-- Statement no2
'After Error!'
-- Statement no3
-- Statement
In the CATCH block we can handle the error and send the error message to the application. So we need an element to show what error occurs. This element is RAISERROR. So the error handling structure could be like this:
RAISERROR(
'Error!!!'
, 16, 1);
The RAISERROR itself needs other elements to identify the error number, error message, etc. Now we can complete the error handling structure:
DECLARE
@ErrorMessage NVARCHAR(4000);
@ErrorSeverity
INT
@ErrorState
@ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE();
RAISERROR (@ErrorMessage,
-- Message text.
@ErrorSeverity,
-- Severity.
-- State.
);
You can test this scenario with the following code:
CREATE
PROCEDURE
spErrorHandler
AS
go
-----------------------------------------
spTest
EXEC
spErrorHandler;
exec
spTest;
As is illustrated in this figure, when using spErrorHandler, the values of ERROR_PROCEDURE() and ERROR_NUMBER() are changed in the output. This behavior is because of the RAISERROR functionality. This function always re-raises the new exception, so spErrorHandler always shows that the value of ERROR_PROCEDURE() simply is “spErrorHandler”. As I said before there are two workarounds to fix this issue. First is concatenating this useful data with the error message and raise it, which I spoke about in reason one. Second is inserting this useful data in another table just before we re-raise the error in spErrorHandler.
Now, we test the above sample without using spErrorHandler:
As you see in this figure, the procedure name and error number are correct. By the way, I prefer that if one customer reports an error, I go for SQL Server Profiler, simulate the environment completely, and test those SQL statements in SSMS to recreate the error and debug it based on the correct error number and procedure name.
In the THROW section, I will explain that the main advantage of THROW over RAISERROR is that it shows the correct line number of the code that raises the error, which is so helpful for a developer in debugging his code.
To improve this mechanism Microsoft SQL Server 2012 introduced the THROW statement. Now I will address the benefits of THROW over RAISERROR.
As I said earlier this is the main advantage of using THROW. The following code will enlighten this great feature:
create
proc sptest
as
set
nocount
on
1/0
declare
@msg nvarchar(2000) = error_message();
raiserror( @msg , 16, 1);
THROW
sptest
As you can see in this figure, the line number of the error that RAISERROR reports to us always is the line number of itself in the code. But the error line number reported by THROW is line 6 in this example, which is the line where the error occurred.
Another benefit of using the THROW statement is that there is no need for extra code in RAISERROR.
The severity level raised by THROW is always 16. But the more important feature is that when the THROW statement in a CATCH block is executed, then other code after this statement will never run. The following sample script shows how this feature protects the code compared to RAISERROR:
TABLE
#Saeid (id
int
)
INSERT
#Saeid
VALUES
( 101 );
*
FROM
#Saeid;
DROP
'This will never print!!!'
This feature makes it possible to re-throw custom message numbers without the need to use sp_addmessage to add the number.
THROW 60000,
'This a custom message!'
, 1;
“How can I terminate the execution of the TRY block?”
The answer is using THROW in the TRY block. Its severity level is 16, so it will terminate execution in the TRY block. We know that when any statement in the TRY block terminates (encounters an error) then immediately execution goes to the CATCH block. In fact the main idea is to THROW a custom error as in this code:
As you can see, we handle the error step by step. In the next session we will complete this structure.
This is the misconception that I sometimes hear. I explain this problem with a little example. After executing the following code the table “dbo.Saeid” still exists. This demonstrates that the TRY/CATCH block does not implement implicit transactions.
PROC sptest
dbo.Saeid
--No1
( id
--No2
-------------------------------------------
sptest;
dbo.Saeid;
“Where is the right place to commit and rollback? “
It’s a complex discussion that I would not like to jump into in this article. But there is a simple template that we can use for procedures (not triggers!). This is that template:
XACT_ABORT
--set xact_abort option
TRAN
--begin transaction
dbo.Hasani
COMMIT
--commit transaction
IF @@TRANCOUNT > 0
--check if there are open transaction?
ROLLBACK
TRAN;
--rollback transaction
dbo.Hasani;
The elements of this structure are:
In general it’s recommended to set the XACT_ABORT option to ON in our TRY/CATCH block in procedures. By setting this option to ON if we want to roll back the transaction, any user defined transaction is rolled back.
We check this global variable to ensure there is no open transaction. If there is an open transaction it’s time to execute rollback statements. This is a must in all CATCH blocks, even if you do not have any transactions in that procedure. An alternative is to use XACT_STATE().
Now I have some connectivity problems and some errors for upload figures and codes. I am trying to solve this issues!
There are still some clutter after copy from word. I will correct this article format as soon as possible. Now I have to go sleep. My local time is 4:57 am!!!
Naomi N edited Revision 6. Comment: Grammas, typos, more tags
This is a good article. I have few suggestions: 1) Add See Also section with links to several articles about SQL Error Handling as well as general T-SQL articles (check my recent articles for examples). 2) You may need to re-do a few of the images because you had typos in the text messages which I corrected, but they are still in the images. 3) Don't forget to add this article (and other T-SQL articles) to the list of all T-SQL articles ( we need to keep it up current). Thanks in advance
Thanks so much Naomi,
I will apply your suggestions as soon as possible.
Still working on its format!
Saeid Hasani edited Revision 13. Comment: Add see also section.
Saeid Hasani edited Revision 14. Comment: minor format.
Saeid Hasani edited Revision 16. Comment: Format many things!
Saeid Hasani edited Revision 18. Comment: remove toc