So I finally had an opportunity to use Try...Catch in the real world, and I found the examples to be a little lacking around executing a stored proc. Hopefully this will help. If not, it will at least help me remeber when I forget it in a week.
In the old days we would trap for errors something like this.
declare @rc int
declare @msg varchar(3000)
exec @rc=usp_some_proc
if @rc <> 0 or @@Error <> 0
begin
set @msg = 'Error in usp_some_proc'
goto ERRORPROC
end
goto ENDPROC
ERRORPROC:
BEGIN
RAISERROR (@msg, 16, 1)
ENDPROC:
Essentially we had to trap for errors in each proc individually, which was quite tedious.
In SQL Server 2005 we get a new error handling setup much like .NET languages using the Try...Catch construct. Much has been made of this feature and how to use it and the other new error handling functions, so I won't repeat that here. Essentially what I want to make note of is the ability of T-SQL to bubble up the errors to the calling procedure. As I mentioned, this is _lightly_ documented. Imagine the following
BEGIN TRY
exec usp_some_proc1
exec usp_some_proc2
exec usp_some_proc3
END TRY
BEGIN CATCH
PRINT 'This error has been caught...'
PRINT 'Msg ' + cast(ERROR_NUMBER() as varchar) + ', Level ' + cast(ERROR_SEVERITY() as varchar) + ', State ' + cast(ERROR_STATE() as varchar) + ', Line ' + cast(ERROR_LINE() as varchar)
PRINT ERROR_MESSAGE()
END CATCH
Let's say all three procs catch and raise errors like the old method above. When the error is thrown in child proc, the outer script will catch the error and drop to the catch block, thus bubbling it up the error, with little toil and trouble to you. One interesting thing that is missing. It is not possible to re-throw the error already thrown by the child. You have to log another error or print out the results (unless I'm missing something...). And finally, yeah, what about Finally? Here's hoping we get that last piece of that construct in a service pack soon. It's a really nice feature in .NET that I wish we had in T-SQL.
If you want further proof of bubbling, take a look at this example. Thanks to Chip Downs for providing it.
/*******************************************************************
Test out all of the different error level types...
0 - 10 are warnings
11 - 19 are trapable errors
20 - 25 are terminal errors
*******************************************************************/
IF
EXISTS (SELECT distinct 1 FROM sysobjects where name = 'Test_Error_Levels')
DROP PROCEDURE Test_Error_Levels
GO
CREATE
PROCEDURE Test_Error_Levels
@ErrorLevel INTEGER
AS
DECLARE @ErrorMessage as varchar(100)
If @ErrorLevel between 0 and 10
SET @ErrorMessage = 'This message is a warning and is only for informational purposes'
If @ErrorLevel between 11 and 19
SET @ErrorMessage = 'This is a trapable error'
If @ErrorLevel >= 20
SET @ErrorMessage = 'This is a terminal error for test purposes and is being logged... (can only be raised by an admin).'
If @ErrorLevel >= 20
RAISERROR (@ErrorMessage, @ErrorLevel, 1) WITH LOG
ELSE
RAISERROR (@ErrorMessage, @ErrorLevel, 1)
GO
/*
Test out warnings
Warning: Null value is eliminated by an aggregate or other SET operation
*/
DECLARE
@caught AS BIT
SET
@caught = 0
BEGIN
TRY
EXEC Test_Error_Levels 5
PRINT 'This is the line following the warning.'
END
TRY
BEGIN
CATCH
PRINT 'This error has been caught...'
PRINT 'Msg ' + cast(ERROR_NUMBER() as varchar) + ', Level ' + cast(ERROR_SEVERITY() as varchar) + ', State ' + cast(ERROR_STATE() as varchar) + ', Line ' + cast(ERROR_LINE() as varchar)
PRINT ERROR_MESSAGE()
SET @caught = 1
END
CATCH
IF
@caught = 0
PRINT 'This error was not caught...'
GO
--Test out errors
BEGIN
TRY
EXEC Test_Error_Levels 13
PRINT 'This is the line following the error.'
END
TRY
BEGIN
CATCH
PRINT 'This error has been caught...'
PRINT 'Msg ' + cast(ERROR_NUMBER() as varchar) + ', Level ' + cast(ERROR_SEVERITY() as varchar) + ', State ' + cast(ERROR_STATE() as varchar) + ', Line ' + cast(ERROR_LINE() as varchar)
PRINT ERROR_MESSAGE()
END
CATCH
GO
--Test out terminal errors (Connection will be broken)
BEGIN
TRY
EXEC Test_Error_Levels 24
END
TRY
BEGIN
CATCH
PRINT 'This error has been caught...'
PRINT 'Msg ' + cast(ERROR_NUMBER() as varchar) + ', Level ' + cast(ERROR_SEVERITY() as varchar) + ', State ' + cast(ERROR_STATE() as varchar) + ', Line ' + cast(ERROR_LINE() as varchar)
PRINT ERROR_MESSAGE()
END
CATCH
GO
So there it is. Hopefully this helps someone out there who is finding it hard to determine how this works from the existing documentation.
Jon