Exception flow in a stored procedure calling another stored procedure - sql-server

I've got a stored procedure that calls another stored procedure. SP2 has a try/transaction/catch/rollback/raiserror. If SP2 raises an error, will it bubble up through SP1 to the caller, or do I have to nest the call to SP2 in a try/catch as well? If the latter is the case, how can I assure that I'm not killing the "stack trace" of the error from SP2 when raising/exiting SP1?
-- will this bubble any error from SP2 and exit SP1 as well?
EXEC dbo.storedProc2 #someParameter = #someValue
--Or do I need to do this?
BEGIN TRY
EXEC dbo.storedProc2 #someParameter = #someValue
END TRY
BEGIN CATCH
-- this is what I normally do in a simple catch/raise scenario:
-- will it kill the error stack?
DECLARE #ErrMsg VARCHAR(4000), #ErrSeverity INT, #ErrState INT, #ErrLine INT
SELECT #ErrMsg = ERROR_MESSAGE() + ' Line %d',
#ErrSeverity = ERROR_SEVERITY(),
#ErrState = ERROR_STATE(),
#ErrLine = ERROR_LINE()
RAISERROR(#ErrMsg, #ErrSeverity, #ErrState, #ErrLine)
END CATCH

You will need to have an outer TRY/CATCH block.
If you only have the error trapping in SP2, when SP2 errors it will abort.
It will then pass the error code back to SP1, but SP1 will continue to execute under most circumstances (exception would be a fatal error with severity 20-25).
There's no real inherent error trapping without using TRY/CATCH (or checking ##Error if you are old-school) - the code will continue to execute, for better or worse.

You should trap and deal with errors WITHIN dbo.storedProc2
Then return error codes using the return n method
SP1 can then deal with that error(s) in whatever way is required.

Related

SQL Stored Procedure Exception Not Found in JAVA

Throwing exception from sql stored procedure catch block is not raising exception in java layer for SP1 while for SP2 exception raised.
When I am executing both SPs are giving exception in SQL Server but from java code I got no exception for SP1.
I am calling both SPs using JPA StoredProcedureQuery and using Hibernate & Spring.
CREATE PROCEDURE SP1
AS
SET NOCOUNT ON
BEGIN TRY
DECLARE #ErrorMessage AS VARCHAR(MAX)
SELECT 1/0
END TRY
BEGIN CATCH
THROW 50001, #ErrorMessage, 1;
END CATCH
CREATE PROCEDURE SP2
AS
SET NOCOUNT ON
DECLARE #ErrorMessage AS VARCHAR(MAX)
SELECT 1/0
if ##error<>0
GOTO Erro
RETURN
Erro:
THROW 50001, #ErrorMessage, 1;
I have tested that if I use any THROW in TRY block it raise exception in Java side while in CATCH block its not.

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

Try and Catch on TSQL - catch not catching

I have a stored procedure that seems not to be logging its errors correctly.
The code is erroring, but the catch block doesn't seem to be coming into effect.
The try block is fairly long - but the erroring section is simple and comes rightat the end, so I've precis'd that.
BEGIN TRY
insert into tbl_X
select * from #temp_tbl_Y
RETURN 1
END TRY
BEGIN CATCH
Insert Into ExtractsErrorLog
SELECT
getdate() as ErrorDate
,object_name(##procid) as ProcedureName
,ERROR_NUMBER() as ErrorNumber
,ERROR_LINE() as ErrorLine
,ERROR_MESSAGE() as ErrorMessage
;
DECLARE #errormessage as varchar(max);
DECLARE #errorseverity as int;
DECLARE #errorstate as int;
set #errormessage = ERROR_MESSAGE();
set #errorseverity = ERROR_SEVERITY();
set #errorstate = ERROR_STATE();
RAISERROR (#errormessage,
#errorseverity,
#errorstate
);
END CATCH;
The error the proc is failing on is our old friend
"Column name or number of supplied values does not match table definition."
I've fixed that error - It was a dumb lazy mistake - but I'm baffled why my error logging process didn't seem to be working - no row is being inserted into my ExtractsErrorLog table.
TSQL's TRY...CATCH does not catch that error. This error falls into the "compilation/recompilation" type errors that are not handled by the CATCH block "within the same level of execution".
From MSDN:
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 can use TRY…CATCH to handle errors that occur during compilation
or statement-level recompilation by executing the error-generating
code in a separate batch within the TRY block. For example, you do
this by placing the code in a stored procedure or by executing a
dynamic Transact-SQL statement using sp_executesql. This allows
TRY…CATCH to catch the error at a higher level of execution than the
error occurrence. For example, the following code shows a stored
procedure that generates an object name resolution error. The batch
that contains the TRY…CATCH construct is executing at a higher level
than the stored procedure; and the error, which occurs at a lower
level, is caught.
I ran into similar issues with a script creating a transaction inside a TRY...CATCH that would ROLLBACK the transaction if it failed. A statement inside the transaction was throwing that same error and caused the transaction to never be closed, as the CATCH was never entered.
As mentioned in the MSDN article, one alternative is to create a stored procedure out of your INSERT statement and then call that inside your try/catch. If the sproc is wrong, you'll catch the compilation error while trying to create it. If the table definition later changes to invalidate the sproc, then the TRY...CATCH will catch the exception for you.
If you want it to all live in one script, you can make it a temporary stored procedure, but you will then need to handle the compilation errors while you are creating the sprocs. It's not pretty, but it will work:
-- Creating error sproc to re-use code
CREATE PROCEDURE #HandleError AS
Insert Into ExtractsErrorLog
SELECT GETDATE() as ErrorDate
,object_name(##procid) as ProcedureName
,ERROR_NUMBER() as ErrorNumber
,ERROR_LINE() as ErrorLine
,ERROR_MESSAGE() as ErrorMessage;
DECLARE #errormessage as varchar(max);
DECLARE #errorseverity as int;
DECLARE #errorstate as int;
set #errormessage = ERROR_MESSAGE();
set #errorseverity = ERROR_SEVERITY();
set #errorstate = ERROR_STATE();
RAISERROR ( #errormessage,
#errorseverity,
#errorstate);
GO
-- Create a stored procedure of our INSERT and catch any compilation errors
CREATE PROCEDURE #TEST AS
insert into tbl_X
select * from #temp_tbl_Y
GO
IF (##ERROR <> 0) BEGIN
exec #HandleError
-- If there was an error creating the sprocs, don't continue to the next batch
RETURN
END
-- If compilation succeeded, then run the sproc
BEGIN TRY
exec #TEST
RETURN
END TRY
BEGIN CATCH
exec #HandleError
END CATCH;
I used THROW in my CATCH block before the INSERT statement for my logging - and got the same issue as you. Once I moved THROW after the logging INSERT statement it worked. Looks like THROW might terminate the session.
You don't use THROW in your code example, but thought this might help someone else.
It's your RETURN: "Exits unconditionally from a query or procedure. RETURN is immediate and complete and can be used at any point to exit from a procedure, batch, or statement block."

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;

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.

Resources