Why the procedure is not going into the catch block - sql-server

I have a stored procedure which is selecting the entry from the Entity table which doesn't exists in our database.
In the code below if I execute the stored procedure, it's not going into catch block while for every error in try block it should go to the catch block automatically.
I am not able to understand the reason
Create PROCEDURE AddUpdateEntity
(#Name VARCHAR(20),
#Age SMALLINT)
AS
BEGIN TRY
SELECT NAME, Age FROM Entity WHERE NAME = #name AND Age = #Age
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() StatusCode, ERROR_MESSAGE() [Message]
END CATCH
GO
/* Command to execute the SP */
EXEC AddUpdateEntity 'Sandeep',20
This execute statement is showing the error "Invalid Entity Object" but not calling the catch block.
The stored procedure has crashed and is showing the message
Msg 208, Level 16, State 1, Procedure AddUpdateEntity, Line 10
Nom d'objet 'Entity' non valide.

As per the MSDN (follow link http://msdn.microsoft.com/en-us/library/ms175976.aspx)
Errors Unaffected by a TRY…CATCH Construct
TRY…CATCH constructs do not trap the following conditions:
Warnings or informational messages that have a severity of 10 or lower.
Errors that have a severity of 20 or higher that stop the SQL Server Database Engine task processing for the session. If an error occurs that has severity of 20 or higher and the database connection is not disrupted, TRY…CATCH will handle the error.
Attentions, such as client-interrupt requests or broken client connections.
When the session is ended by a system administrator by using the KILL statement.
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:
Compile errors, such as syntax errors, that prevent a batch from running.
Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.
These errors are returned to the level that ran the batch, stored procedure, or trigger.
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.
Hope the above description will solve your problem.

It will never enter the CATCH block simply because no rows are returned from a query. A sql query which returns no rows isn't an error; it's expected behavior.
As others have pointed out, the error you're seeing appears to be a compile-time error, because the Entity table does not exist.
You could try using dynamic sql with sp_executesql to force the SP to check for the tables existence only at run-time.
BEGIN TRY
DECLARE #sqlStr NVARCHAR(4000),
#sqlParams NVARCHAR(400);
SET #sqlStr = N'SELECT Name, Age FROM Entity WHERE Name=#Name AND Age=#Age';
SET #sqlParams = '#Name VARCHAR(20), #Age SMALLINT';
sp_executesql #sqlStr, #sqlParams, #Name = #Name, #Age = #Age
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() StatusCode, ERROR_MESSAGE() [Message]
END CATCH

Related

SQL Server TRY...CATCH Is Not Catching An Error

BEGIN TRY
EXEC N'EXEC sp_testlinkedserver N''[MyLinkedServer]'';';
END TRY
BEGIN CATCH
SELECT 'LinkedServerDown' AS Result
RETURN
END CATCH
SELECT TOP(1) FirstName FROM [MyLinkedServer].TestDatabase.dbo.Customer
My first experience with using a TRY...CATCH in SQL Server does not have me impressed so far.
I've stopped the SQL Service on my linked server to attempt to test a situation where our linked server is down, inaccessible, etc.
Instead of catching any error, this code just throws the "Login timeout expired" and "network-related or instance-specific error has occurred..." error and ceases execution of the rest of the code.
Is my SQL TRY...CATCH block not set up correctly?
As per the MSDN, what sp_testlinkedserver do is
Tests the connection to a linked server. If the test is unsuccessful
the procedure raises an exception with the reason of the failure.
So when you compile your code (SP), sp_testlinkedserver checks for connection. But you can defer this and capture it by using dynamic SQL.
Like this -
BEGIN TRY
EXEC sp_executesql N'EXEC sp_testlinkedserver [192.168.51.81];';
END TRY
BEGIN CATCH
SELECT 'LinkedServerDown' AS Result
END CATCH
From MSDN
Errors Unaffected by a TRY…CATCH Construct
TRY…CATCH constructs do not trap the following conditions:
Warnings or informational messages that have a severity of 10 or
lower.
Errors that have a severity of 20 or higher that stop the SQL Server
Database Engine task processing for the session. If an error occurs
that has severity of 20 or higher and the database connection is not
disrupted, TRY…CATCH will handle the error.
Attentions, such as client-interrupt requests or broken client
connections.
When the session is ended by a system administrator by using the
KILL statement.
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:
Compile errors, such as syntax errors, that prevent a batch from
running.
Errors that occur during statement-level recompilation, such as
object name resolution errors that occur after compilation because
of deferred name resolution.
You need to create your end testlinkedserver stored procedure. This will also capture login time out errors.
exec dbo.USP_testlinkedserver 'myServerNameHere'
The definition is mentioned below:
CREATE PROCEDURE USP_testlinkedserver
#ServerName sysname
AS
BEGIN
SET NOCOUNT ON;
DECLARE #statement NVARCHAR(MAX), #errorMessage NVARCHAR(MAX)
SET #statement = N'SELECT * FROM OPENQUERY('+QUOTENAME(#ServerName,'[')+', ''SELECT 1'')'
BEGIN TRY
-- run the query
EXEC sp_executesql #stmt = #statement;
END TRY
BEGIN CATCH
-- show custom message
SET #errorMessage=QUOTENAME(#ServerName,'[') + ' linked server is not available. ' + ERROR_MESSAGE()
Raiserror(#errorMessage,16,1)
END CATCH;
END

What happens when I return from inside a SQL cursor?

I have the following code inside a cursor in a stored procedure I am working with:
SELECT #err_code = ##error
If #err_code <> 0
BEGIN
ROLLBACK TRAN
Select return_status = 'FAIL',
return_msg = 'Insert Into Errs Warnings Failed !!'
return 16
END
Before this error checking there is some table inserts (still inside the cursor). Recently I encountered an error where the table we were inserting into didn't match the insert statement that was coded (Column name or number of supplied values does not match table definition). I would have thought that this error checking code would have caught this and killed the procedure, but for some reason all it did was print an error message to the log and continue running. Column name or number of supplied values does not match table definition was the error message.
So I'm wondering if this has to do with the return being inside of a cursor. Is is possible that in this scenario all the return 16 would have done was exit the cursor and continue to execute the rest of the stored procedure? Or should it have terminated the procedure entirely? I'm using MS SQL Server 2008.
Thank you!
The issue with this ##ERROR function is , its scope is very limited. It will only be populated if an error occurs in the statement that was executed just before calling ##ERROR function.
If any other statement is executed before or called after that error occurred the ##ERROR will return NULL.
Also to anticipate where error may occur, and capture its value intime and stop the execution there, its a bit of a pain and error prone.
The best way of error handling in sql server would be try..catch block.
BEGIN TRY
BEGIN TRANSACTION
-- your code here
COMMIT TRANSACTION --<-- this will only be executed if nothing
-- goes wrong in prior statements
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
Select return_status = 'FAIL',
return_msg = 'Insert Into Errs Warnings Failed !!'
END CATCH
Using Try...Catch block also gives you access to Error functions which can only be used in CATCH BLOCK and can help you to get detailed information about the error occurred in the Try..Block.
error functions like ERROR_MESSAGE(), ERROR_LINE() , ERROR_STATE(), ERROR_PROCEDURE() etc

How do I catch an error from a stored proc within a stored proc in VBA

I have a stored procedure that calls several others, one of which is failing to insert a row into a table due to a duplicate primary key
The error raised is
Msg 2627, Level 14, State 1, Procedure ..., Line 16
Violation of PRIMARY KEY constraint '...'. Cannot insert duplicate key in object '...'.
I am calling the this from an Excel spreadsheet via VBA, with usual On Error handling in place, but the routine is failing silently without triggering the error.
I'm not sure if this is down to the stored-proc within stored-proc or the severity of the error being too low.
Has anyone experienced anything like this and can suggest a work around?
My initial attempt was to put a BEGIN TRY / BEGIN CATCH block around the stored procedure call, with the CATCH running RAISERROR at a higher severity, but it doesn't seem to be triggering.
Thanks
In the outer proc add an explicit transaction. BEGIN TRANSACTION at the beginning and COMMIT TRANSACTION at the end.
Then before the begin transaction add SET XACT_ABORT ON;. That will take care of batch failures.
After the inner proc with the error, check the error value for statement level errors e.g.
IF ##ERROR <> 0
BEGIN
ROLLBACK TRANSACTION;
RETURN 1;
END
We do this with OUTPUT variables in T-SQL, though I only know the SQL side of it: you'll need to google getting an output parameter back from SQL with VBA. Declare an output variable as a varchar, and set it immediately to '':
DECLARE #MyError VARCHAR(500) OUTPUT
SET #MyError = ''
Do your normal error checking in T-SQL and, if you do find an error, SET a description into the #MyError variable. Your VBA code will always get the #MyError message, but it will normally be an empty string, ''. If it isn't, then you go to your error handling in VBA.

SQL Server error logging from a Stored Procedure

Our application is Windows Service (native .EXE written in C++) that calls stored procedures in SQL Server. In most cases errors in stored procedures (in 90% of the cases these errors mean something was wrong in our business logic) are re-thrown as exception and caught by our service. They are then logged in Application Event Log on the computer where our service is running.
However, I now have a need to log some of the errors on the SQL Server itself within a stored procedure.
Following the paradigm we use for our service I think I can use xp_logevent to save error information in the event log.
Is this a recommended approach to log SQL Server errors?
FWIW I use SQL Server 2008
The How To
You can always use RAISEERROR() WITH LOG. Logs to both Windows Application log and the SQL error log.Please note that severity level is key here. There are some limitations and security considerations, but you get some other features also.
More details in BOL:
http://msdn.microsoft.com/en-us/library/ms178592.aspx
The Should you
My opinion is that you shouldn't log anything to SQL error log unless it's generated by SQL server itself. Multiple reasons:
If your IT or DBA uses log analyzer or any other tool, it may trip an alarm on an application issue, instead of the server issue (this is what they are trying to catch).
I never found parsing error logs enjoyable from within SQL server, and I'm not particularly in love with SSMS's way of doing it.
My suggestion
Use a generic logging stored procedure writing to some error log table. A very nice patter is
BEGIN TRY
...do your stuff
END TRY
BEGIN CATCH
get the ERROR_LINE(), ERROR_MESSAGE() and friends
execute generic logging procedure
END CATCH
As a bonus, you can use SSSB within the logging procedure to make it async and not impede the main logic flow
Here is a useful way I have found to keep track of SQL Server errors. First, create a table to store the errors:
CREATE TABLE utiliity.dbo.ProcedureLog
(
LogDate DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
DatabaseID INT,
ObjectID INT,
ProcedureName NVARCHAR(400),
ErrorLine INT,
ErrorMessage NVARCHAR(MAX),
AdditionalInfo NVARCHAR(MAX)
);
GO
CREATE CLUSTERED INDEX cx_LogDate ON dbo.utiliity.dbo.ProcedureLog(LogDate);
GO
Then create a stored procedure to call when the error occurs:
CREATE PROCEDURE sp_CallProcedureLog
#ObjectID INT,
#DatabaseID INT = NULL,
#AdditionalInfo NVARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#ProcedureName NVARCHAR(400);
SELECT
#DatabaseID = COALESCE(#DatabaseID, DB_ID()),
#ProcedureName = COALESCE
(
QUOTENAME(DB_NAME(#DatabaseID)) + '.'
+ QUOTENAME(OBJECT_SCHEMA_NAME(#ObjectID, #DatabaseID))
+ '.' + QUOTENAME(OBJECT_NAME(#ObjectID, #DatabaseID)),
ERROR_PROCEDURE()
);
INSERT utiliity.dbo.ProcedureLog
(
DatabaseID,
ObjectID,
ProcedureName,
ErrorLine,
ErrorMessage,
AdditionalInfo
)
SELECT
#DatabaseID,
#ObjectID,
#ProcedureName,
ERROR_LINE(),
ERROR_MESSAGE(),
#AdditionalInfo;
END
GO
Finally, in your stored procedures where you want to record the errors:
BEGIN TRY
... execute SQL commands here
END TRY
BEGIN CATCH
DECLARE #msg NVARCHAR(MAX);
SET #msg = 'Something went horribly wrong. Error number = ' + ERROR_NUMBER();
EXEC utiliity.dbo.sp_CallProcedureLog
#ObjectID = ##PROCID,
#AdditionalInfo = #msg;
DECLARE #ErrorMessage NVARCHAR(MAX);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
-- Use RAISERROR inside the CATCH block to return error
-- information about the original error that caused
-- execution to jump to the CATCH block.
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH
Here are my sources: http://www.mssqltips.com/sqlservertip/2003/simple-process-to-track-and-log-sql-server-stored-procedure-use/ and http://msdn.microsoft.com/en-us/library/ms178592(SQL.105).aspx. HTH.
You can call xp_logevent to log messages in the event log. But for logging exceptions it is better to use the RAISERROR () WITH LOG statement.
If you are concerned about performance you can pass the message through a SQL Server Service Broker queue and have an activation procedure log the messages in the eventlog.
The downside is that whoever has to find out the errors now needs permissions to get into the event log.
If you go with this, make sure your log has more size than the default 512K. Also set it to overwrite events as needed.
Also, the event log is not as fast as your SQL Server database so you may want to run a load test to figure out if it slows your application down.

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

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

Resources