Archive
Standardize TRY CATCH block across all Stored Procedures – MSDN TSQL forum
–> Question:
I am trying to develop best try catch block to be standardized in all of the SPs. Below is what I have right now developed which is giving me proper formatted output, would like to add more details or remove unwanted items from it.
Thanks for all your help!
I am using 2008 R2 Version, would be migrating to 2012 soon.
BEGIN CATCH IF @@TRANCOUNT > 0 BEGIN ROLLBACK TRANSACTION END DECLARE @ErrorMsg varchar(2100); SET @ErrorMsg = ' DBName = ' + DB_NAME() + CHAR(10) + ' ErrorMessage = ' + LTRIM(CONVERT(VARCHAR(2047), LEFT(Error_Message(),2044))) + CHAR(10) + ' Procedure = ' + OBJECT_NAME(@@PROCID) + CHAR(10) + ' ErrorNumber = ' + LTRIM(CONVERT(VARCHAR(9),Error_Number())) + CHAR(10) + ' ErrorState = ' + LTRIM(CONVERT(VARCHAR(3),Error_State())) + CHAR(10) + ' ErrorSeverity = ' + LTRIM(CONVERT(VARCHAR(3),Error_Severity())) + CHAR(10) + ' LineNumber = ' + LTRIM(CONVERT(VARCHAR(9),Error_Line())) + CHAR(10) + ' ErrorDT = ' + CONVERT(VARCHAR(23),GETDATE(),121) + CHAR(10) + ' ErrorBy = ' + SUSER_SNAME(); RAISERROR(@ErrorMsg,16,1); RETURN -1 END CATCH
–> My Answer:
The way you’ve drafted your TRY-CATCH looks good, however you can also consider few things:
1. Use THROW instead of RAISERROR as recommended by Microsoft, this will also reduce unnecessary (at times) declaration and assignment of error related variables, link.
2. With THROW you can also catch multiple errors, RAISERROR will only throw last error raised, link.
3. Make use of XACT_ABORT only when you want to rollback the entire transaction, link.
Check all these and other stuff about TRY-CATCH in my blog posts.
Ref Link.
Capture multiple errors in TRY CATCH by using THROW statement
This post relates to my earlier post [link] where I mentioned on benefit of using THROW clause with same SQL examples.
The THROW clause was introduced in SQL Server 2012 and may be replacing the RAISERROR function in near future.
Normally the SQL statements returns single error, but some SQL statements returns more than one error message when they go wrong due to some reason or exception.
–> On executing the below BACKUP statement in SSMS we can see we get two errors:
BACKUP DATABASE [AdventureWorks2012] TO DISK='E:\FOLDER_NOT_EXISTS\test.bak'
The above code throws 2 errors with Error-Message IDs 3201 & 3013, as shown below:
error messages:
Msg 3201, Level 16, State 1, Line 2
Cannot open backup device ‘E:\FOLDER_NOT_EXISTS\test.bak’. Operating system error 3(The system cannot find the path specified.).
Msg 3013, Level 16, State 1, Line 2
BACKUP DATABASE is terminating abnormally.
-> But when we want to track these errors by using RAISERROR function it just returns the last (single) error message and its details, and the previous error message details are not returned by this function.
BEGIN TRY BACKUP DATABASE [AdventureWorks2012] TO DISK='E:\FOLDER_NOT_EXISTS\test.bak' END TRY BEGIN CATCH DECLARE @msg VARCHAR(1000) = ERROR_MESSAGE() RAISERROR(@msg,16,0) END CATCH
Here, only 1 error message will be returned:
error messages:
Msg 50000, Level 16, State 0, Line 7
BACKUP DATABASE is terminating abnormally.
–> With the new THROW clause you won’t see any issue of omitting the previous errors, as it returns all error details thrown by the SQL Statement itself.
BEGIN TRY BACKUP DATABASE [AdventureWorks2012] TO DISK='E:\FOLDER_NOT_EXISTS\test.bak' END TRY BEGIN CATCH THROW; END CATCH
The above statement throws both the error details as we saw in the first example:
error messages:
Msg 3201, Level 16, State 1, Line 2
Cannot open backup device ‘E:\FOLDER_NOT_EXISTS\test.bak’. Operating system error 3(The system cannot find the path specified.).
Msg 3013, Level 16, State 1, Line 2
BACKUP DATABASE is terminating abnormally.
Thus, if you are on SQL Server 2012 and above you must consider using THROW clause instead of the RAISERROR function.
Most effective way to write SQL Statement to Catch and Reverse errors during Query execution – MSDN TSQL forum
–> Question:
I am wondering what is the most effective way to deal with errors, specifically in a Stored Procedure.
I wrote something like this:
BEGIN TRY BEGIN TRANSACTION /*My statements goes in here*/ IF ERROR_NUMBER() = 0 -- Do I need this line? COMMIT TRANSACTION; END TRY BEGIN CATCH IF ERROR_NUMBER() > 0 --Do I need this line? ROLLBACK TRANSACTION; END CATCH;
It would make sense using the if Statement when attempting to log errors.
–> My Answer:
Checking ERROR_NUMBER() within TRY BEGIN-COMMIT TRANS block is not a good idea, because as soon as an error is reported the control will be immediately redirected to the CATCH block.
So, use it like this:
BEGIN TRY BEGIN TRANSACTION /*My statements goes in here*/ COMMIT TRANSACTION; END TRY BEGIN CATCH ;THROW ROLLBACK TRANSACTION; END CATCH;
Check my other blog posts on error/exception handling.
Ref Link.
Another reason to use THROW clause instead of RAISERROR | SQL Server 2012
In my previous post [link] I talked about the new THROW clause introduced in SQL Server 2012 and how it is different from the existing RAISERROR function.
I just stumbled on one more reason to stop using RAISERROR function and start using the new THROW clause.
There are some SQL statements that throws more than one error message when they go wrong due to some reason.
-> When using RAISERROR function it just returns the last (single) error message and its details, but the previous error message details are not returned by this function.
-> With the new THROW clause you won’t see any issue of omitting the previous errors. And it returns all error details as thrown by the SQL statement itself.
Let’s check this with a small example where we want to take backup of a database, but the folder provided does not exist, and hence it errors out:
Example #1:
On executing the below BACKUP statement we can see we get 2 errors:
BACKUP DATABASE [AdventureWorks2012] TO DISK='E:\FOLDER_NOT_EXISTS\test.bak'
The above code throws 2 errors with Error-Message IDs 3201 & 3013, as shown below:
error messages:
Msg 3201, Level 16, State 1, Line 2
Cannot open backup device ‘E:\FOLDER_NOT_EXISTS\test.bak’. Operating system error 3(The system cannot find the path specified.).
Msg 3013, Level 16, State 1, Line 2
BACKUP DATABASE is terminating abnormally.
Example #2:
And when you try to use error-handling by using TRY-CATCH and RAISERROR(), it fetches us only 1 error:
BEGIN TRY BACKUP DATABASE [AdventureWorks2012] TO DISK='E:\FOLDER_NOT_EXISTS\test.bak' END TRY BEGIN CATCH DECLARE @msg VARCHAR(1000) SET @msg = ERROR_MESSAGE() RAISERROR(@msg,16,0) END CATCH
Here, only 1 error message will be returned:
error messages:
Msg 50000, Level 16, State 0, Line 7
BACKUP DATABASE is terminating abnormally.
Example #3:
But this is not the case with the new THROW clause, as I mentioned previously.
It throws all the errors that are originally thrown by the original SQL statement, like below:
BEGIN TRY BACKUP DATABASE [AdventureWorks2012] TO DISK='E:\FOLDER_NOT_EXISTS\test.bak' END TRY BEGIN CATCH THROW; END CATCH
The above statement throws both the error details as we saw in the first example:
error messages:
Msg 3201, Level 16, State 1, Line 2
Cannot open backup device ‘E:\FOLDER_NOT_EXISTS\test.bak’. Operating system error 3(The system cannot find the path specified.).
Msg 3013, Level 16, State 1, Line 2
BACKUP DATABASE is terminating abnormally.
Thus, you must consider using THROW clause instead of the RAISERROR function if you are in SQL Server 2012.
But, it still depends upon different conditions and scenarios where you would need either of both the features.
TRY CATCH – Exception Handling in SQL Server
Similar to C++, Java and other languages SQL Server also has a mechanism to handle exceptions by using TRY-CATCH construct. The TRY block contains the SQL statements that may raise an error and CATCH block contains the handling mechanism to process the error. When any error is raised in the TRY block the control is immediately transferred to the CATCH block, where the Error is handled.
–> Following rules should be taken care off while using TRY-CATCH constructs:
– A TRY block must be followed immediately by the CATCH block.
– Both TRY & CATCH blocks must be inside a Batch, Stored Procedure or a Trigger.
– Only Errors with severity between 10 & 20 that do not close the database connection are caught & handled by TRY-CATCH constructs.
– As per MS BOL, Errors that have a severity of 20 or higher that cause the Database Engine to close the connection will not be handled by the TRY…CATCH block. And Errors that have a severity of 10 or lower are considered warnings or informational messages, and are not handled by TRY…CATCH blocks.
–> let’s check how to use TRY-CATCH block:
USE [tempdb] GO --// Create a test Stored Procedure CREATE PROC testPrc (@val VARCHAR(10)) AS BEGIN SELECT 1/@val AS operation END GO --// Test for Divide by 0 (Divide by zero error encountered.) BEGIN TRY EXEC testPrc '0' END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ERROR_ID, ERROR_MESSAGE() AS ERROR_MSG, ERROR_SEVERITY() AS ERROR_SEVERITY, ERROR_STATE() AS ERROR_STATE, ERROR_PROCEDURE() AS ERROR_PROCEDURE, ERROR_LINE() AS ERROR_LINE END CATCH GO --// Test for Datatype conversion (Conversion failed when converting the varchar value 'a' to data type int.) BEGIN TRY EXEC testPrc 'a' END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ERROR_ID, ERROR_MESSAGE() AS ERROR_MSG, ERROR_SEVERITY() AS ERROR_SEVERITY, ERROR_STATE() AS ERROR_STATE, ERROR_PROCEDURE() AS ERROR_PROCEDURE, ERROR_LINE() AS ERROR_LINE END CATCH GO --// Test nested TRY-CATCH for "Divide by 0" & "Datatype conversion" errors both. BEGIN TRY EXEC testPrc 'a' END TRY BEGIN CATCH SELECT 'outer block', ERROR_NUMBER() AS ERROR_ID, ERROR_MESSAGE() AS ERROR_MSG, ERROR_SEVERITY() AS ERROR_SEVERITY, ERROR_STATE() AS ERROR_STATE, ERROR_PROCEDURE() AS ERROR_PROCEDURE, ERROR_LINE() AS ERROR_LINE BEGIN TRY SELECT 1/0 AS operation END TRY BEGIN CATCH SELECT 'inner block', ERROR_NUMBER() AS ERROR_ID, ERROR_MESSAGE() AS ERROR_MSG, ERROR_SEVERITY() AS ERROR_SEVERITY, ERROR_STATE() AS ERROR_STATE, ERROR_PROCEDURE() AS ERROR_PROCEDURE, ERROR_LINE() AS ERROR_LINE END CATCH END CATCH GO --// Test for violation of PK Constraint (Violation of PRIMARY KEY constraint 'PK__testTable__2C3393D0'. Cannot insert duplicate key in object 'dbo.testTable'.) BEGIN TRY CREATE TABLE testTable (a INT PRIMARY KEY) INSERT INTO testTable VALUES(1) INSERT INTO testTable VALUES(1) END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ERROR_ID, ERROR_MESSAGE() AS ERROR_MSG, ERROR_SEVERITY() AS ERROR_SEVERITY, ERROR_STATE() AS ERROR_STATE, ERROR_PROCEDURE() AS ERROR_PROCEDURE, ERROR_LINE() AS ERROR_LINE END CATCH GO SELECT * FROM testTable -- Contains single record with value 1 --// Test for recreating a table that already exists (There is already an object named 'testTable' in the databASe.) BEGIN TRY CREATE TABLE testTable (a INT PRIMARY KEY) END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ERROR_ID, ERROR_MESSAGE() AS ERROR_MSG, ERROR_SEVERITY() AS ERROR_SEVERITY, ERROR_STATE() AS ERROR_STATE, ERROR_PROCEDURE() AS ERROR_PROCEDURE, ERROR_LINE() AS ERROR_LINE END CATCH GO --// Test for inserting NULL value on Primary Key column (Cannot insert the value NULL into column 'a', table 'tempdb.dbo.testTable'; column does not allow nulls. INSERT fails.) BEGIN TRY INSERT INTO testTable VALUES(NULL) END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS ERROR_ID, ERROR_MESSAGE() AS ERROR_MSG, ERROR_SEVERITY() AS ERROR_SEVERITY, ERROR_STATE() AS ERROR_STATE, ERROR_PROCEDURE() AS ERROR_PROCEDURE, ERROR_LINE() AS ERROR_LINE END CATCH GO --// Final Cleanup DROP TABLE testTable DROP PROC testPrc GO
MS BOL Links for TRY-CATCH:
http://msdn.microsoft.com/en-us/library/ms175976.aspx
http://msdn.microsoft.com/en-us/library/ms179296%28v=SQL.90%29.aspx
http://msdn.microsoft.com/en-us/library/ms179495.aspx (Error Information)