One of the common complaints about writing code in T-SQL has always been its lack of robust exception handling construct. Up through SQL Server 2000 we could write a T-SQL batch of code that checked for and even raised errors when needed. But the techniques to do so are fairly rudimentary when compared to the TRY/CATCH techniques. First I will define the problems associated with exception handling using the @@ERROR function and then I will show how the new TRY/CATCH technique is different from this.
SQL Server exposes the built-in @@ERROR function, which returns the error number for the last T-SQL command that was executed. The problem with this function is that it always returns the error returned from the statement that was executed just before the current statement. This means that if you execute an INSERT statement that causes an error, then execute another SQL statement of any kind that does not cause an error, and then check to see what @@ERROR returns, the function will return a 0 because the previous statement did not return an error. You have to be very careful to make sure you check the value of @@ERROR after every single statement.
BEGIN TRANSACTION
DELETE OrderDetails WHERE OrderID IN (SELECT OrderID FROM Orders WHERE CustomerID = 'ABC')
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
RETURN
END
DELETE Orders WHERE CustomerID = 'ABC'
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
RETURN
END
DELETE Customers WHERE CustomerID = 'ABC'
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
RETURN
END
Notice that I had to follow every statement with the @@ERROR function and then follow it up with a ROLLBACK and a RETURN. This approach can get really ugly. Imagine writing a transaction that contains dozens of queries that all need to be checked. The process could be simplified somewhat by using GOTO statements,but they still don't solve the problem of needing to check the @@ERROR function immediately following the action query.
SQL Server 2005 still supports the @@ERROR function but it also includes the widely known TRY/CATCH paradigm. The TRY/CATCH construct is similar to the C# try/catch construct in that errors are trapped within the try block and execution is then transferred to the catch block. (The T-SQL version of TRY/CATCH has no FINALLY clause option as does the C# version. You can simulate one by catching the exceptions, not returning, and then following the catch block with appropriate cleanup code.) Therefore you need to watch out for this in certain circumstances, like when you have a CURSOR allocated and open at that time an error is thrown in the T-SQL TRY block. In this case the cursor should be checked in the CATCH block to see if it is open; if it is open, it should be closed and deallocated.
BEGIN TRY
BEGIN TRANSACTION
DELETE OrderDetails WHERE OrderID IN (SELECT OrderID FROM Orders
WHERE CustomerID = 'ABC')
DELETE Orders WHERE CustomerID = 'ABC'
DELETE Customers WHERE CustomerID = 'ABC'
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
RETURN
END CATCH
For example, we could add the following statement inside of the CATCH block to return information about that error:
SELECT
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage,
ERROR_NUMBER() AS ErrorNumber,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState