What is wrong with my Try Catch in T-SQL? - sql-server

I am using SQL Server 2008 and when I run this Statement in Management studio the Select statement in the Catch Block is executed as expected
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
However when I run this statement the statement in the Catch Block is never executed and instead the error is just displayed in the results tab
BEGIN TRY
Select * from IDontExist
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
They both return the same error number '208' 'Invalid Object Name: IDontExist' so why would one get handled and the other not?

I don't get the CATCH block hit at all.
That's because the code won't compile, because the object doesn't exist, no plan is generated, so nothing runs to hit the CATCH block.
You can never hit this catch block so somethign is wrong with your testing/example. You can hit an outer catch block in a different scope (eg nested stored procs)
Edit: I'm using SQL Server 2005 SP3
It depends when deferred name resolution applies, related to statement level recompilation.
In my case, the whole batch fails both times and no statement level recompilation happens so no deferred name resolution
In OP's case, the batch compiles and runs but then has a statement level recompilation/deferred name resolution error in running code
I'm off to find some references about why it's different, given BOL doesn't say much, neither does Erland Sommarskog

This has bitten me in the past as well.
Not all errors generated inside the TRY block statements are passed into the CATCH block. Any errors with a severity of 10 or less are considered to be warnings and do not cause control to flow to the CATCH block. Also, any errors that break the database connection will not cause the CATCH block to be reached. There may be other situations as well.

Directly from http://msdn.microsoft.com/en-us/library/ms175976.aspx.
USE AdventureWorks2008R2;
GO
BEGIN TRY
-- Table does not exist; object name resolution
-- error not caught.
SELECT * FROM NonexistentTable;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH
The error is not caught and control passes out of the TRY…CATCH construct to the next higher level.
Running the SELECT statement inside a stored procedure will cause the error to occur at a level lower than the TRY block. The error will be handled by the TRY…CATCH construct.

This behaviour happens if you previously had a table IDontExist and compiled a plan for it that is still in the cache then drop the table.
It also happens if you run the individual statement twice even without the table ever existing. The first run raises an error that is not caught. The second run (after the first plan is cached) succeeds.
/*Clear Cache*/
DBCC FREEPROCCACHE
GO
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
/*Plan now Cached*/
SELECT query_plan
FROM sys.dm_exec_cached_plans cp
OUTER APPLY sys.dm_exec_sql_text(plan_handle) t
OUTER APPLY sys.dm_exec_query_plan(plan_handle) qp
WHERE t.text LIKE '%IDontExist%'
OPTION (RECOMPILE)
GO
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
The INSERT statement gets auto parameterised.
If you change your Select * from IDontExist statement to Select * from IDontExist WHERE ProductID = 1 this also becomes auto parameterised and they behave the same.
I'm not absolutely certain why the auto parameterisation makes a difference here. I think that it is explained by the below extract from BOL however.
The following types of errors are not handled by a CATCH block when they occur at the same level of execution as the TRY…CATCH construct ... [those] that occur during statement-level recompilation ... If an error occurs during compilation or statement-level recompilation at a lower execution level (for example, when executing sp_executesql or a user-defined stored procedure) inside the TRY block, the error
occurs at a lower level than the TRY…CATCH construct and will be handled by the associated CATCH block.
I presume the auto parametrization of that statement means that it gets recompiled at a lower execution level and is catchable.

Now that we have all the explanations as to why this is happening. Let's see an actual solution to the problem.
First let's take the statements that #d-k-mulligan proposed above and turn them into stored procs.
IF OBJECT_ID('dbo.prcIDontExistINSERT', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistINSERT
GO
CREATE PROCEDURE dbo.prcIDontExistINSERT
AS
BEGIN TRY
INSERT INTO IDontExist(ProductID)
VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
IF OBJECT_ID('dbo.prcIDontExistSELECT', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistSELECT
GO
CREATE PROCEDURE dbo.prcIDontExistSELECT
AS
BEGIN TRY
SELECT * FROM IDontExist
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
If we run either of them we see the same error.
EXEC dbo.prcIDontExistINSERT
EXEC dbo.prcIDontExistSELECT
Msg 208, Level 16, State 1, Procedure prcIDontExistSELECT, Line 4
Invalid object name 'IDontExist'.
The solution now is to create error handling wrapper procs with the sole purpose of catching any error from the original procs above that are getting the object not found errors.
IF OBJECT_ID('dbo.prcIDontExistInsert_ERROR_HANDLER', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistInsert_ERROR_HANDLER
GO
CREATE PROCEDURE dbo.prcIDontExistInsert_ERROR_HANDLER
AS
BEGIN TRY
EXEC dbo.prcIDontExistINSERT
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
IF OBJECT_ID('dbo.prcIDontExistSELECT_ERROR_HANDLER', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistSELECT_ERROR_HANDLER
GO
CREATE PROCEDURE dbo.prcIDontExistSELECT_ERROR_HANDLER
AS
BEGIN TRY
EXEC dbo.prcIDontExistSELECT
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO
Finally, let's run either of our error handling procs and see the message we expect.
EXEC dbo.prcIDontExistInsert_ERROR_HANDLER
EXEC dbo.prcIDontExistSELECT_ERROR_HANDLER
There was an error! Invalid object name 'IDontExist'.
NOTE: Kalman Toth did all the hard research work here:
http://www.sqlusa.com/articles2008/trycatch/

Workaround with dynamic sql. Maybe it will be helpful for someone.
begin try
exec('
insert into IDontExist(ProductID)
values(1)
')
end try
begin catch
select 'There was an error! ' + error_message()
end catch

Related

SQL Server Exception Handling

I am trying to understand what exactly is meant by TRY-CATCH does not catch compilation errors that occur in the same scope.
I am currently trying to set up exception handling in SQL Server and I came across this statement a couple of times, but don't quite understand what it means. I notice that if I have an INSERT statement, the exception handling works for PRIMARY KEY constraint errors, but it doesn't seem to work for INSERT statements that include an extra column.
it doesn't seem to work for INSERT statements that include an extra column.
Right, because TRY-CATCH does not catch compilation errors that occur in the same scope.
If your compilation error happens in a nested scope, your catch block will run.
Here's a simple example of the behavior:
create table t(id int)
go
begin try
insert into t(id) values (1,2)
end try
begin catch
select concat('catch block ', error_message())
end catch
go
begin try
exec ('insert into t(id) values (1,2)');
end try
begin catch
select concat('catch block ', error_message())
end catch

Position of TRY/CATCH bloc for SQL Server stored procedure

I would like to know the best position of a TRY/CATCH for a T-SQL procedure and why
In the statement:
CREATE PROCEDURE procedure_name
AS
BEGIN
-- Code
BEGIN TRY
sql_statement
END TRY
BEGIN CATCH
-- Handle errors
END CATCH
--Code
END
or in the call:
BEGIN TRY
EXEC procedure_name
END TRY
BEGIN CATCH
-- Handle errors
END CATCH
I would go for the first option.
BEGIN TRY
sql_statement
END TRY
BEGIN CATCH
-- Handle errors
END CATCH
The reason is you would want to catch the errors at the source and then take some appropriate actions.
In second option you are letting the error bubble up and there you would not have access to all the Exact error information returned by the error functions inside the catch block.
For example the ERROR_LINE() function will return the line number of the calling procedure where the it is calling the procedure containing the actual sql code, you would want to know the error line number where the actual exception was thrown, this information is only available in the catch block of the procedure being called.
Moral of the story is try to catch exceptions as close to the source as possible.

SQL Server Stored Procedure Error Handling

I have a stored procedure which is runs automatically every morning in SQL Server 2008 R2, part of this stored procedure involves executing other stored procedures. The format can be summarised thus:
BEGIN TRY
-- Various SQL Commands
EXECUTE storedprocedure1
EXECUTE storedprocedure2
-- etc
END TRY
BEGIN CATCH
--This logs the error to a table
EXECUTE errortrappingprocedure
END CATCH
storedprocedure1 and storedprocedure2 basically truncate a table and select into it from another table. Something along the lines of:
BEGIN TRY
TRUNCATE Table1
INSERT INTO Table1 (A, B, C)
SELECT A, B, C FROM MainTable
END TRY
BEGIN CATCH
EXECUTE errortrappingprocedure
END CATCH
The error trapping procedure contains this:
INSERT INTO
[Internal].dbo.Error_Trapping
(
[Error_Number],
[Error_Severity],
[Error_State],
[Error_Procedure],
[Error_Line],
[Error_Message],
[Error_DateTime]
)
(
SELECT
ERROR_NUMBER(),
ERROR_SEVERITY(),
ERROR_STATE(),
ERROR_PROCEDURE(),
ERROR_LINE(),
ERROR_MESSAGE(),
GETDATE()
)
99% of the time this works, however occasionally we will find that storedprocedure1 hasn't completed, with Table1 only being part populated. However no errors are logged in our error table. I've tested the error trapping procedure and it does work.
When I later run storedprocedure1 manually it completes fine. No data in the source table will have changed by this point so it's obviously not a problem with the data, something else has happened in that instant which has caused the procedure to fail. Is there a better way for me to log errors here, or somewhere else within the database I can look to try and find out why it is failing?
Try to use SET ARITHABORT (see link). It must ROLLBACK in your case. Also the answer of #Kartic seem reasonable.
I recommned also to read about implicit and explicit transactions - I think that this is your problem. You have several implicit transactions and when error happeneds you are in the middle of the job - so only part is rollbackеd and you have some data in that tables.
There are some type of Errors that TRY..CATCH block will not handle them, look here for more information https://technet.microsoft.com/en-us/library/ms179296(v=sql.105).aspx . for such Errors you should handle them in your application.
also I think you might have transaction management problem in your application too.
I am not sure if I understood you completely. Below code is too big for comment. So posting as an answer for your reference. If this is not what you want, I'll delete it.
Can we add transaction handling part as well.
DECLARE #err_msg NVARCHAR(MAX)
BEGIN TRY
BEGIN TRAN
-- Your code goes here
COMMIT TRAN
END TRY
BEGIN CATCH
SET #err_msg = ERROR_MESSAGE()
SET #err_msg = REPLACE(#err_msg, '''', '''''')
ROLLBACK TRAN
-- Do something with #err_msg
END CATCH

How to log warnings (low-severity errors) in SQL 2008 R2

I would like to log warnings thrown from my Transact SQL scripts that aren't going to get caught in a TRY...CATCH block. Is there any way to do this? ERROR_NUMBER(), etc. won't work outside of a catch block and I'm unsure of how to even know to know a warning was thrown. Googling hasn't yielded much.
The documentation seems to intend that the error message be passed pack to the caller. It does also however state that if you wrap the statements in a stored procedure, and then call that one within a try-catch block, you will catch low-severity errors.
-- Verify that the stored procedure does not exist.
IF OBJECT_ID ( N'usp_ExampleProc', N'P' ) IS NOT NULL
DROP PROCEDURE usp_ExampleProc;
GO
-- Create a stored procedure that will cause an
-- object resolution error.
CREATE PROCEDURE usp_ExampleProc
AS
SELECT * FROM NonexistentTable;
GO
BEGIN TRY
EXECUTE usp_ExampleProc;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
You cannot catch these errors with a try catch even if you wrap it in a proc and try. Here is an example.
CREATE PROC P
AS
BEGIN
RAISERROR('TEST',9,-1,-1)
END;
BEGIN TRY
EXEC P
END TRY
BEGIN CATCH
PRINT 'CAUGHT'
END CATCH;

Select statement on non-existing columns not being caught by exception block

I'm expecting an error here since X_TEST column doesn't exists.
But the error is not being caught by the exception block.
BEGIN TRY
SELECT X_TEST FROM ACCOUNTS
END TRY
BEGIN CATCH
PRINT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
Why ? Is it because of the severity of the error ?
No, TRY can only handle certain types of errors within its own scope. Now, you say you have a stored procedure that looks like this (after non_existent_column has been dropped):
CREATE PROCEDURE dbo.blat
AS
BEGIN
BEGIN TRY
SELECT non_existent_column FROM dbo.table_that_exists;
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
END CATCH
END
GO
If you simply do this...
EXEC dbo.blat;
...you will get hammered with a compilation error because the statement within that TRY's scope fails to parse:
Msg 207, Level 16, State 1, Procedure fooblat, Line 5
Invalid column name 'non_existent_column'.
However you can catch this error at an outer scope (whether the procedure has TRY/CATCH or not):
BEGIN TRY
EXEC dbo.fooblat;
END TRY
BEGIN CATCH
PRINT 'There was an error:';
PRINT ERROR_MESSAGE();
END CATCH
Result (notice this is not an exception and the text is no longer red):
There was an error:
Invalid column name 'non_existent_column'.
You can also avoid this with dynamic SQL, but it seems like you've already shot that down, so this is the next best suggestion I can think of - capture the error at an outer scope.
It is a syntax error to use a non-existing column. Your batch never even starts to execute.
Consider this code:
BEGIN TRY
sdgedtju§$%&/()=
END TRY
BEGIN CATCH
PRINT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
You would not expect it to even start, right? It does not compile. Using a non-existing column is just the same
This code would give you the behavior you were expecting to see...
BEGIN TRY
exec('SELECT X_TEST FROM ACCOUNTS')
END TRY
BEGIN CATCH
PRINT 'There was an error! ' + ERROR_MESSAGE()
END CATCH

Resources