IF, RAISERROR & RETURN in Stored Procedure - sql-server-2005

I have a stored procedure, PROC, which receives some parameters. If one of them, #ID, is not null, a given stored procedure, PROC_A, must be executed. Otherwise, PROC_B must be executed. The problem is that both of them may issue a RAISERROR, which I want to propagate through the call stack to be displayed at the client application. However, that RAISERROR won't stop the rest of the PROC stored procedure as it should, and, since I am using an IF clause, checking IF ( ##ERROR <> 0 ) RETURN isn't an option either. My only choice seems to be using a TRY...CATCH block to wrap the IF clause and rethrow the RAISERROR from within the CATCH block, which is awkwards because then I will have to cache ERROR_MESSAGE(), ERROR_SEVERITY() and ERROR_STATE() and use RAISERROR once again.
Isn't there really any more elegant way?

just use a TRY - CATCH block and echo back the original error, which isn't that hard to do:
BEGIN TRY
--used in the CATCH block to echo the error back
DECLARE #ErrorMessage nvarchar(400), #ErrorNumber int, #ErrorSeverity int, #ErrorState int, #ErrorLine int
--Your stuff here
END TRY
BEGIN CATCH
--your error code/logging here
--will echo back the complete original error message
SELECT #ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),#ErrorNumber = ERROR_NUMBER(),#ErrorSeverity = ERROR_SEVERITY(),#ErrorState = ERROR_STATE(),#ErrorLine = ERROR_LINE()
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState, #ErrorNumber,#ErrorLine)
END CATCH
Also, it is best practice to have your entire procedure in a TRY - CATCH, and not just the external procedure calls.

Related

SQL Server 2008 R2 Transaction is ##error necessary and is ROLLBACK TRANS necessary

My colleague has this in a procedure:
BEGIN TRAN
--Some deletes and inserts
IF(##error <> 0)
BEGIN
ROLLBACK TRAN
RETURN
END
COMMIT TRAN
I have another in a stored procedure that simply is:
BEGIN TRANSACTION
--Some deltes and inserts
COMMIT TRANSACTION
I have tested and found that my procedure always rolls everything back during an error (tested for example changing a column data type etc.) without explicitly coding a rollback. Also I have read that using ##error condition is outdated for SQL Server 2005 and above.
What would you say is the correct way of doing a transaction for SQL Server 2008 R2 and above? Thanks
YES, the ROLLBACK is necessary!
I would do a stored procedure based on this template for SQL Server 2005 and newer:
BEGIN TRANSACTION
BEGIN TRY
-- put your T-SQL commands here
-- if successful - COMMIT the work
COMMIT TRANSACTION
END TRY
BEGIN CATCH
-- handle the error case (here by displaying the error)
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
-- in case of an error, ROLLBACK the transaction
ROLLBACK TRANSACTION
-- if you want to log this error info into an error table - do it here
-- *AFTER* the ROLLBACK
END CATCH
There a problem with the ##ERROR variable.
It's a global variable thus if you are doing something like:
BEGIN TRAN
--inserts
--deletes
--updates
-- last operation
IF(##error <> 0)
BEGIN
ROLLBACK TRAN
RETURN
END
COMMIT TRAN
##error contains the result for the last operation only. Thus this piece of code can mask error in previous operations.
My advice is, if you can manage transaction at application level, do it at application level.
Handling errors at server side is not for faint hearts and it doesn't improves your application overral robusteness.
Create the following procedure in your DB then in your catch block, simply exec RethrowError.
The nice thing about this is that you dont have to pass any parameters into it from your main stored procedure
CREATE PROCEDURE [dbo].[RethrowError] AS
-- Return if there is no error information to retrieve.
IF ERROR_NUMBER() IS NULL
RETURN;
DECLARE
#ErrorMessage NVARCHAR(4000),
#ErrorNumber INT,
#ErrorSeverity INT,
#ErrorState INT,
#ErrorLine INT,
#ErrorProcedure NVARCHAR(200);
-- Assign variables to error-handling functions that
-- capture information for RAISERROR.
SELECT
#ErrorNumber = ERROR_NUMBER(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorLine = ERROR_LINE(),
#ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
-- Building the message string that will contain original
-- error information.
SELECT #ErrorMessage =
N'Error %d, Level %d, State %d, %s, Line %d' + ERROR_MESSAGE();
-- Raise an error: msg_str parameter of RAISERROR will contain
-- the original error information.
RAISERROR
(
#ErrorMessage,
#ErrorSeverity,
1,
#ErrorNumber, -- parameter: original error number.
#ErrorSeverity, -- parameter: original error severity.
#ErrorState, -- parameter: original error state.
#ErrorProcedure, -- parameter: original error procedure name.
#ErrorLine -- parameter: original error line number.
);
GO
CREATE PROCEDURE YourProcedure
AS
BEGIN TRANSACTION
BEGIN TRY
--Put your code in here
END TRY
BEGIN CATCH
EXEC RethrowError
END CATCH
END

tsql transaction

In order to wrap stored procedure in a transaction I add the following:
CREATE PROCEDURE [dbo].[P_ORD_InsertTextField]
//PARAMS
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
//STP BODY
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK
DECLARE #ErrMsg nvarchar(4000), #ErrSeverity int
SELECT #ErrMsg = ERROR_MESSAGE(),
#ErrSeverity = ERROR_SEVERITY()
RAISERROR(#ErrMsg, #ErrSeverity, 1)
END CATCH
END
GO
Is there any shorter way that does the same? this is a huge code block for "just" handle a transaction..
No, this is pretty much it.
You can hide the #ErrMsg processing behind a stored proc or UDF, and you don't need #ErrSeverity processing. It is normally 16 which is "user defined error"
See my answer here too please: Nested stored procedures containing TRY CATCH ROLLBACK pattern?

How to rethrow the same exception in SQL Server

I want to rethrow the same exception in SQL Server that has just occurred in my try block. I am able to throw same message but I want to throw same error.
BEGIN TRANSACTION
BEGIN TRY
INSERT INTO Tags.tblDomain (DomainName, SubDomainId, DomainCode, Description)
VALUES(#DomainName, #SubDomainId, #DomainCode, #Description)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
declare #severity int;
declare #state int;
select #severity=error_severity(), #state=error_state();
RAISERROR(##Error,#ErrorSeverity,#state);
ROLLBACK TRANSACTION
END CATCH
RAISERROR(##Error, #ErrorSeverity, #state);
This line will show error, but I want functionality something like that.
This raises error with error number 50000, but I want the error number to be thrown that I am passing ##error,
I want to capture this error no at the frontend.
i.e.
catch (SqlException ex)
{
if ex.number==2627
MessageBox.show("Duplicate value cannot be inserted");
}
I want this functionality. which can't be achieved using raiseerror. I don't want to give custom error message at back end.
RAISEERROR should return below mentioned error when I pass ErrorNo to be thrown in catch
Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,
Line 14
Violation of UNIQUE KEY constraint 'UK_DomainCode'. Cannot insert
duplicate key in object
'Tags.tblDomain'.
The statement has been terminated.
EDIT:
What can be the drawback of not using try catch block if I want exception to be handled at frontend considering stored procedure contains multiple queries that need to be executed?
SQL 2012 introduces the throw statement:
http://msdn.microsoft.com/en-us/library/ee677615.aspx
If the THROW statement is specified without parameters, it must appear
inside a CATCH block. This causes the caught exception to be raised.
BEGIN TRY
BEGIN TRANSACTION
...
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
THROW
END CATCH
Here is a fully functional clean code sample to rollback a series of statements if an error occurs and reports the error message.
begin try
begin transaction;
...
commit transaction;
end try
begin catch
if ##trancount > 0 rollback transaction;
throw;
end catch
Before SQL 2012
begin try
begin transaction;
...
commit transaction;
end try
begin catch
declare #ErrorMessage nvarchar(max), #ErrorSeverity int, #ErrorState int;
select #ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), #ErrorSeverity = ERROR_SEVERITY(), #ErrorState = ERROR_STATE();
if ##trancount > 0 rollback transaction;
raiserror (#ErrorMessage, #ErrorSeverity, #ErrorState);
end catch
Rethrowing inside the CATCH block (pre-SQL2012 code, use THROW statement for SQL2012 and later):
DECLARE
#ErrorMessage nvarchar(4000) = ERROR_MESSAGE(),
#ErrorNumber int = ERROR_NUMBER(),
#ErrorSeverity int = ERROR_SEVERITY(),
#ErrorState int = ERROR_STATE(),
#ErrorLine int = ERROR_LINE(),
#ErrorProcedure nvarchar(200) = ISNULL(ERROR_PROCEDURE(), '-');
SELECT #ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 'Message: ' + #ErrorMessage;
RAISERROR (#ErrorMessage, #ErrorSeverity, 1, #ErrorNumber, #ErrorSeverity, #ErrorState, #ErrorProcedure, #ErrorLine)
I think your choices are:
Dont catch the error (let it bubble up)
Raise a custom one
At some point, SQL will probably introduce a reraise command, or the ability to catch only certain errors. But for now, use a workaround. Sorry.
You can't: only the engine can throw errors less than 50000. All you can do is throw an exception that looks like it...
See my answer here please
The questioner here used client side transactions to do what he wanted which I think is a wee bit silly...
Ok, this is a workaround...:-)
DECLARE #Error_Number INT
BEGIN TRANSACTION
BEGIN TRY
INSERT INTO Test(Id, Name) VALUES (newID(),'Ashish')
/* Column 'Name' has unique constraint on it*/
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER()
--RAISERROR (#ErrorMessage,#Severity,#State)
ROLLBACK TRAN
END CATCH
If you note the catch block, It is not raising the error but returning the actual error number (and also would rollback the transaction). Now in your .NET code, instead of catching the
exception, if you use ExecuteScalar(), you get the actual error number you want and show the appropriate number.
int errorNumber=(int)command.ExecuteScalar();
if(errorNumber=<SomeNumber>)
{
MessageBox.Show("Some message");
}
Hope this helps,
EDIT :- Just a note, If you want to get the number of records affected and trying to use ExecuteNonQuery, the above solution may not work for you. Otherwise, I think It would suit what you need. Let me know.
The way to stop execution in a stored procedure after an error has occurred and bubble the error back to the calling program is to follow each statement that might throw an error with this code:
If ##ERROR > 0
Return
I was surprised myself to find out that execution in a stored procedure can continue after an error - not realizing this can lead to some hard to track down bugs.
This type of error handling parallels (pre .Net) Visual Basic 6. Looking forward to the Throw command in SQL Server 2012.
Given that you haven't moved to 2012 yet, one way to implement the bubbling up of the original error code is to use the text message part of the exception you are (re)throwing from the catch block. Remember that it can contain some structure, for example, XML text for your caller code to parse in its catch block.
You can also create a wrapper stored procedure for the those scenarios when you want the SQL statement to be executed within the transaction and feed the error up to your code.
CREATE PROCEDURE usp_Execute_SQL_Within_Transaction
(
#SQL nvarchar(max)
)
AS
SET NOCOUNT ON
BEGIN TRY
BEGIN TRANSACTION
EXEC(#SQL)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
DECLARE #ErrorMessage nvarchar(max), #ErrorSeverity int, #ErrorState int
SELECT #ErrorMessage = N'Error Number: ' + CONVERT(nvarchar(5), ERROR_NUMBER()) + N'. ' + ERROR_MESSAGE() + ' Line ' + CONVERT(nvarchar(5), ERROR_LINE()), #ErrorSeverity = ERROR_SEVERITY(), #ErrorState = ERROR_STATE()
ROLLBACK TRANSACTION
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState)
END CATCH
GO
-- Test it
EXEC usp_Execute_SQL_Within_Transaction #SQL = 'SELECT 1; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction #SQL = 'SELECT 1/0; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction #SQL = 'EXEC usp_Another_SP'
From a design point of view, what is the point of throwing exceptions with original error numbers and custom messages? To some extent it breaks the interface contract between applications and the database.
If you want to catch original errors and handle them in higher code, don't handle them in the database. Then when you catch an exception you can change the message presented to the user to anything you want. I would not do it though, because it makes your database code hmm 'not right'. As others said you should define a set of your own error codes (above 50000) and throw them instead. Then you can hanle integrity issues ('Duplicate values are not allowed') separately from potential business issues - 'Zip code is invalid', 'No rows were found matching the criteria' and so on.

Nested stored procedures containing TRY CATCH ROLLBACK pattern?

I'm interested in the side effects and potential problems of the following pattern:
CREATE PROCEDURE [Name]
AS
BEGIN
BEGIN TRANSACTION
BEGIN TRY
[...Perform work, call nested procedures...]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc]
END CATCH
END
To the best of my understanding this pattern is sound when used with a single procedure - the procedure will either complete all of its statements without error, or it will rollback all actions and report the error.
However when one stored procedure calls another stored procedure to do some sub-unit of work (with the understanding that the smaller procedure is sometimes called on its own) I see an issue coming about with relation to rollbacks - an informational message (Level 16) is issued stating The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.. This I assume is because the rollback in the sub-procedure is always rolling back the outer-most transaction, not just the transaction started in the sub-procedure.
I do want the whole thing rolled back and aborted if any error occurs (and the error reported to the client as an SQL error), I'm just not sure of all the side effects that come from the outer layers trying to rollback a transaction that has already been rolled back. Perhaps a check of ##TRANCOUNT before doing a rollback at each TRY CATCH layer?
Finally there is the client end (Linq2SQL), which has it's own transaction layer:
try
{
var context = new MyDataContext();
using (var transaction = new TransactionScope())
{
// Some Linq stuff
context.SubmitChanges();
context.MyStoredProcedure();
transactionComplete();
}
}
catch
{
// An error occured!
}
In the event that a stored procedure, "MySubProcedure", called inside MyStoredProcedure raises an error, can I be sure that everything previously done in MyStoredProcedure will be rolled back, all the Linq operations made by SubmitChanges will be rolled back, and finally that the error will be logged? Or what do I need to change in my pattern to ensure the whole operation is atomic, while still allowing the child parts to be used individually (i.e. the sub-procedures should still have the same atomic protection)
This is our template (error logging removed)
This is designed to handle
Paul Randal's article "No such thing as a nested transaction in SQL Server"
Error 266
Trigger Rollbacks
Explanations:
all TXN begin and commit/rollbacks must be paired so that ##TRANCOUNT is the same on entry and exit
mismatches of ##TRANCOUNT cause error 266 because
BEGIN TRAN increments ##TRANCOUNT
COMMIT decrements ##TRANCOUNT
ROLLBACK returns ##TRANCOUNT to zero
You can not decrement ##TRANCOUNT for the current scope
This is what you'd think is the "inner transaction"
SET XACT_ABORT ON suppresses error 266 caused by mismatched ##TRANCOUNT
And also deals with issues like this "SQL Server Transaction Timeout" on dba.se
This allows for client side TXNs (like LINQ)
A single stored procedure may be part of distributed or XA transaction, or simply one initiated in client code (say .net TransactionScope)
Usage:
Each stored proc must conform to the same template
Summary
So don't create more TXNs than you need
The code
CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE #starttrancount int
BEGIN TRY
SELECT #starttrancount = ##TRANCOUNT
IF #starttrancount = 0
BEGIN TRANSACTION
[...Perform work, call nested procedures...]
IF #starttrancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND #starttrancount = 0
ROLLBACK TRANSACTION;
THROW;
--before SQL Server 2012 use
--RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc]
END CATCH
GO
Notes:
The rollback check is actually redundant because of SET XACT_ABORT ON. However, it makes me feel better, looks odd without, and allows for situations where you don't want it on
Remus Rusanu has a similar shell that uses save points. I prefer an atomic DB call and don't use partial updates like their article
I am not a Linq guy (and neither is Erland), but he wrote the absolute bibles on error handling. Outside of the complications Linq might add to your problem, all of your other questions should be answered here:
http://www.sommarskog.se/error_handling/Part1.html
(Old link: http://www.sommarskog.se/error_handling_2005.html)
To solve the issue of returning the error number and line number mentioned by #AlexKuznetsov, one can raise the error as such:
DECLARE #ErrorMessage NVARCHAR(4000)
DECLARE #ErrorSeverity INT
DECLARE #ErrorState INT
DECLARE #ErrorLine INT
DECLARE #ErrorNumber INT
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorNumber = ERROR_NUMBER(),
#ErrorLine = ERROR_LINE()
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState, #ErrorNumber, #ErrorLine)
-- #Amanda method above doesnt return correct error number
DECLARE
#ErrorMessage nvarchar(4000),
#ErrorSeverity int,
#ErrorState int,
#ErrorLine int,
#ErrorNumber int
BEGIN TRY
SELECT 1/0; -- CATCH me
END TRY
BEGIN CATCH
DECLARE #err int = ##ERROR
PRINT #err -- 8134, divide by zero
PRINT ERROR_NUMBER() -- 8134
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorNumber = ERROR_NUMBER(),
#ErrorLine = ERROR_LINE()
-- error number = 50000 :(
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState, #ErrorNumber, #ErrorLine)
END CATCH
-- error number = 8134
SELECT 1/0
In case no special error handling needed in CATCH except rethrow and stored procs call chain isn't too long it may be suitable to use such simple template:
create procedure someNestedSP
as
SET XACT_ABORT ON
begin transaction
-- do some work or call some other similar SP
commit transaction
It would also rollback root transaction with all "nested" ones in case of any error but the code is shorter and more straightforward than #gbn's solution. Still XACT_ABORT takes care of most issues mentioned there.
There may be addiotional overhead for transaction nesting but it may be not too high, I guess.

How to add a Try/Catch to SQL Stored Procedure

CREATE PROCEDURE [dbo].[PL_GEN_PROVN_NO1]
#GAD_COMP_CODE VARCHAR(2) =NULL,
##voucher_no numeric =null output
AS
BEGIN
DECLARE #NUM NUMERIC
DECLARE #PNO NUMERIC
SET #PNO = 0
DECLARE #PNO1 NUMERIC
SET #PNO1=0
-- begin transaction
IF NOT EXISTS (select GLDC_NEXT_PRV_NO
FROM GLAS_FINANCIAL_DOCUMENTS
WHERE GLDC_COMP_CODE = #GAD_COMP_CODE
AND GLDC_DOC_CODE = 'JV' )
BEGIN
RAISERROR ('Error in generating provision number..',16,1)
-- ROLLBACK TRANSACTION
END
ELSE
SELECT #PNO=ISNULL(GLDC_NEXT_PRV_NO,0)+1
FROM GLAS_FINANCIAL_DOCUMENTS
WHERE GLDC_COMP_CODE = #GAD_COMP_CODE
AND GLDC_DOC_CODE = 'JV'
UPDATE GLAS_FINANCIAL_DOCUMENTS
SET GLDC_NEXT_PRV_NO = #PNO
WHERE GLDC_COMP_CODE = #GAD_COMP_CODE
AND GLDC_DOC_CODE = 'JV'
set ##VOUCHER_NO=#PNO
--commit transaction
END
In this proc how can I handle try catch for exception?
See TRY...CATCH (Transact-SQL)
CREATE PROCEDURE [dbo].[PL_GEN_PROVN_NO1]
#GAD_COMP_CODE VARCHAR(2) =NULL,
##voucher_no numeric =null output
AS
BEGIN
begin try
-- your proc code
end try
begin catch
-- what you want to do in catch
end catch
END -- proc end
Transact-SQL is a bit more tricky that C# or C++ try/catch blocks, because of the added complexity of transactions. A CATCH block has to check the xact_state() function and decide whether it can commit or has to rollback. I have covered the topic in my blog and I have an article that shows how to correctly handle transactions in with a try catch block, including possible nested transactions: Exception handling and nested transactions.
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(),
#message = ERROR_MESSAGE(), #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
return;
end catch
end
Error-Handling with SQL Stored Procedures
TRY/CATCH error handling can take place either within or outside of a procedure (or both). The examples below demonstrate error handling in both cases.
If you want to experiment further, you can fork the query on Stack Exchange Data Explorer.
(This uses a temporary stored procedure... we can't create regular SP's on SEDE, but the functionality is the same.)
--our Stored Procedure
create procedure #myProc as --we can only create #temporary stored procedures on SEDE.
begin
BEGIN TRY
print 'This is our Stored Procedure.'
print 1/0 --<-- generate a "Divide By Zero" error.
print 'We are not going to make it to this line.'
END TRY
BEGIN CATCH
print 'This is the CATCH block within our Stored Procedure:'
+ ' Error Line #'+convert(varchar,ERROR_LINE())
+ ' of procedure '+isnull(ERROR_PROCEDURE(),'(Main)')
--print 1/0 --<-- generate another "Divide By Zero" error.
-- uncomment the line above to cause error within the CATCH ¹
END CATCH
end
go
--our MAIN code block:
BEGIN TRY
print 'This is our MAIN Procedure.'
execute #myProc --execute the Stored Procedure
--print 1/0 --<-- generate another "Divide By Zero" error.
-- uncomment the line above to cause error within the MAIN Procedure ²
print 'Now our MAIN sql code block continues.'
END TRY
BEGIN CATCH
print 'This is the CATCH block for our MAIN sql code block:'
+ ' Error Line #'+convert(varchar,ERROR_LINE())
+ ' of procedure '+isnull(ERROR_PROCEDURE(),'(Main)')
END CATCH
Here's the result of running the above sql as-is:
This is our MAIN Procedure.
This is our Stored Procedure.
This is the CATCH block within our Stored Procedure: Error Line #5 of procedure #myProc
Now our MAIN sql code block continues.
¹ Uncommenting the "additional error line" from the Stored Procedure's CATCH block will produce:
This is our MAIN procedure.
This is our Stored Procedure.
This is the CATCH block within our Stored Procedure: Error Line #5 of procedure #myProc
This is the CATCH block for our MAIN sql code block: Error Line #13 of procedure #myProc
² Uncommenting the "additional error line" from the MAIN procedure will produce:
This is our MAIN Procedure.
This is our Stored Pprocedure.
This is the CATCH block within our Stored Procedure: Error Line #5 of procedure #myProc
This is the CATCH block for our MAIN sql code block: Error Line #4 of procedure (Main)
Use a single procedure for error handling
On topic of stored procedures and error handling, it can be helpful (and tidier) to use a single, dynamic, stored procedure to handle errors for multiple other procedures or code sections.
Here's an example:
--our error handling procedure
create procedure #myErrorHandling as
begin
print ' Error #'+convert(varchar,ERROR_NUMBER())+': '+ERROR_MESSAGE()
print ' occurred on line #'+convert(varchar,ERROR_LINE())
+' of procedure '+isnull(ERROR_PROCEDURE(),'(Main)')
if ERROR_PROCEDURE() is null --check if error was in MAIN Procedure
print '*Execution cannot continue after an error in the MAIN Procedure.'
end
go
create procedure #myProc as --our test Stored Procedure
begin
BEGIN TRY
print 'This is our Stored Procedure.'
print 1/0 --generate a "Divide By Zero" error.
print 'We will not make it to this line.'
END TRY
BEGIN CATCH
execute #myErrorHandling
END CATCH
end
go
BEGIN TRY --our MAIN Procedure
print 'This is our MAIN Procedure.'
execute #myProc --execute the Stored Procedure
print '*The error halted the procedure, but our MAIN code can continue.'
print 1/0 --generate another "Divide By Zero" error.
print 'We will not make it to this line.'
END TRY
BEGIN CATCH
execute #myErrorHandling
END CATCH
Example Output: (This query can be forked on SEDE here.)
This is our MAIN procedure.
This is our stored procedure.
Error #8134: Divide by zero error encountered.
occurred on line #5 of procedure #myProc
*The error halted the procedure, but our MAIN code can continue.
Error #8134: Divide by zero error encountered.
occurred on line #5 of procedure (Main)
*Execution cannot continue after an error in the MAIN procedure.
Documentation:
In the scope of a TRY/CATCH block, the following system functions can be used to obtain information about the error that caused the CATCH block to be executed:
ERROR_NUMBER() returns the number of the error.
ERROR_SEVERITY() returns the severity.
ERROR_STATE() returns the error state number.
ERROR_PROCEDURE() returns the name of the stored procedure or trigger where the error occurred.
ERROR_LINE() returns the line number inside the routine that caused the error.
ERROR_MESSAGE() returns the complete text of the error message. The text includes the values supplied for any substitutable parameters, such as lengths, object names, or times.
(Source)
Note that there are two types of SQL errors: Terminal and Catchable. TRY/CATCH will [obviously] only catch the "Catchable" errors. This is one of a number of ways of learning more about your SQL errors, but it probably the most useful.
It's "better to fail now" (during development) compared to later because, as Homer says . . .
yep - you can even nest the try catch statements as:
BEGIN TRY
SET #myFixDte = CONVERT(datetime, #myFixDteStr,101)
END TRY
BEGIN CATCH
BEGIN TRY
SET #myFixDte = CONVERT(datetime, #myFixDteStr,103)
END TRY
BEGIN CATCH
BEGIN TRY
SET #myFixDte = CONVERT(datetime, #myFixDteStr,104)
END TRY
BEGIN CATCH
SET #myFixDte = CONVERT(datetime, #myFixDteStr,105)
END CATCH
END CATCH END CATCH
Create Proc[usp_mquestions]
(
#title nvarchar(500), --0
#tags nvarchar(max), --1
#category nvarchar(200), --2
#ispoll char(1), --3
#descriptions nvarchar(max), --4
)
AS
BEGIN TRY
BEGIN
DECLARE #message varchar(1000);
DECLARE #tempid bigint;
IF((SELECT count(id) from [xyz] WHERE title=#title)>0)
BEGIN
SELECT 'record already existed.';
END
ELSE
BEGIN
if #id=0
begin
select #tempid =id from [xyz] where id=#id;
if #tempid is null
BEGIN
INSERT INTO xyz
(entrydate,updatedate)
VALUES
(GETDATE(),GETDATE())
SET #tempid=##IDENTITY;
END
END
ELSE
BEGIN
set #tempid=#id
END
if #tempid>0
BEGIN
-- Updation of table begin--
UPDATE tab_questions
set title=#title, --0
tags=#tags, --1
category=#category, --2
ispoll=#ispoll, --3
descriptions=#descriptions, --4
status=#status, --5
WHERE id=#tempid ; --9 ;
IF #id=0
BEGIN
SET #message= 'success:Record added successfully:'+ convert(varchar(10), #tempid)
END
ELSE
BEGIN
SET #message= 'success:Record updated successfully.:'+ convert(varchar(10), #tempid)
END
END
ELSE
BEGIN
SET #message= 'failed:invalid request:'+convert(varchar(10), #tempid)
END
END
END
END TRY
BEGIN CATCH
SET #message='failed:'+ ERROR_MESSAGE();
END CATCH
SELECT #message;