SQL SERVER 2008R2 Nested Transactions with RAISERROR - sql

We are going through a process of switching from DB2 to SQL Server 2008R2 and I'm a bit unfamiliar with TSQL. Any help getting a better understanding of what is occurring would be nice. We've created a procedure called RethrowError as:
CREATE PROCEDURE RethrowError
AS
BEGIN
-- Return if there is no error information to retrieve.
IF ERROR_NUMBER() IS NULL
RETURN;
PRINT 'yo error';
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(), '-');
-- Build the message string that will contain original
-- error information.
SELECT #ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' +
'Message: '+ ERROR_MESSAGE();
PRINT 'yo doin something';
-- 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.
);
PRINT 'yo end';
RETURN;
END
GO
The reason we created the procedure is purely to expand on errors in the future without having to touch all the procedures. I've added some PRINT lines for debugging purposes.
My main question is that we have procedure A and on failure it executes RethrowError and I'll see the messages
yo error
yo doin something
yo end
as expected.
CREATE PROCEDURE dbo.A
AS
BEGIN
SET NOCOUNT ON;
DECLARE & SET VARIABLES;
BEGIN TRY
BEGIN TRANSACTION MaintainTarget
DO SOME STUFF
END TRY
BEGIN CATCH
EXEC RethrowError;
IF (XACT_STATE()) = -1
BEGIN
PRINT
N'The transaction is in an uncommittable state. ' +
'Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
IF (XACT_STATE()) = 1
BEGIN
PRINT
N'The transaction is committable. ' +
'Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
RETURN -101;
END CATCH;
RETURN;
END
GO
However, we've created a procedure that executes many procedures and when a nested procedure (ie procedure A being called by procedure B) fails the only messages I see are
yo error
yo doin something
I'm don't quite understand why the last message no longer shows up.
The procedure B is similar to procedure A but with a little difference in the catch.
CREATE PROCEDURE dbo.B
AS
BEGIN
SET NOCOUNT ON;
DECLARE & SET VARIABLES;
BEGIN TRY
DO SOME STUFF
END TRY
BEGIN CATCH
COMMIT;
RETURN -101;
END CATCH;
RETURN;
END
Any help on getting a better understanding what is happening would be appreciated.

I allowed myself to edit your code to mimic the bahaviour, but keep it simple (your job, actually ;).
Your procA works fine, because RethrowError procedure is being called inside CATCH block of procA and everything executes. But in your second case, it all still happens inside TRY block of procB! So CATCH part of procB fires immediately after RAISERROR in RethrowError is called.
This simple example demonstrates this behaviour of TRY-CATCH:
begin try
select 1/0
print 'doesnt show - div error'
end try
begin catch
print 'oops'
select 1/0
print 'this one shows because its in CATCH!'
end catch
And here's your simplified code:
-- "proc B" start
begin try
-- "proc A" start (works fine alone)
begin try
begin tran
select 1/0 --error
end try
begin catch
print 'yo error';
RAISERROR ('RE from RethrowError', 16, 1) --comment this out and see what happens
print 'yo end';
IF (XACT_STATE())=-1 or (XACT_STATE())=1
BEGIN
PRINT N'Rolling back transaction.'
ROLLBACK TRANSACTION;
end
end catch -- "proc A" ends
end try
begin catch
select error_message(), error_severity(), error_state() --
print 'outer catch';
commit;
end catch;
Hope this helps.

Related

Cannot raise/throw exception inside CATCH block SQL

I need to throw/raise an exception in SQL to catch in C# application. However, whenever I throw an error inside CATCH block the error is not actually thrown.
Here is the SQL code I am using:
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO Transactions (Id, Amount)
VALUES (NEWID(), 43346);
INSERT INTO Transactions (Id, Amount)
VALUES (NEWID(), 'fas');
COMMIT TRANSACTION
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE #ErrorSeverity INT = ERROR_SEVERITY();
DECLARE #ErrorState INT = ERROR_STATE();
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState);
THROW 50001, #ErrorMessage, #ErrorSeverity;
END CATCH;
I tried both THROW and RAISERROR methods. However, none of them works. I tested it in my app and in DBeaver but I still cannot raise an exception. Moreover, when I raise an exception outside of CATCH block, then the exception is raised using both THROW and RAISERRORmethods. Not sure what I am doing wrong.

Rollback done - yet this error : Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements

I am getting this error in stored procedure.
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
I read through some of the answers and found that if I return without a commit, I get the error. My stored procedure is something like this:
BEGIN TRY
BEGIN
if #id is null
BEGIN
set #id= (SELECT last_sequence_value FROM table_name WHERE sequence_name = 'id') + 1
BEGIN
BEGIN TRANSACTION
-- update SQL statement here
IF ##ROWCOUNT = 0
BEGIN
ROLLBACK TRANSACTION
RAISERROR('There was an error getting unique id in the table.',10,1)
RETURN
END
IF (##ERROR <> 0)
BEGIN
ROLLBACK TRANSACTION
RAISERROR('There was an error updating record to the table',10,1)
RETURN
END
COMMIT TRANSACTION
END
END
else
BEGIN
-- some sql select statements
END
END
END TRY
BEGIN CATCH
-- Raise an error with the details of the exception
RAISERROR(#ErrMsg, #ErrSeverity, 1) WITH SETERROR
END CATCH
From the above code, I am doing a rollback and returning from the stored procedure. Yet when I run this in the perf testing environment, I get the error mentioned earlier.
Kindly help regarding this.
I have fixed it by adding
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
in the catch block before I raise an error.
Thanks everyone for the wonderful inputs.
I had a user report this same error in code I inherited, but it was caused by a combination of using a transaction and try/catch inside of the sproc along with calling the sproc inside of a VB.Net System.Transactions.TransactionScope block as well. If an error cropped up inside the sproc, the sproc rolled back everything there correctly, but it apparently rolled back the VB.Net transaction as well and created the mismatch. My fix was to remove the transaction code and the the try/catch blocks from the sproc since the VB.Net transaction will rollback all the inserts/updates inside the sproc if the sproc failed or if the other processing done inside the VB.Net transaction block failed as well. Hopefully this will save someone else a day of frustration.
Here you should use try catch blog inside begin transaction. like this
BEGIN
if #id is null
BEGIN
set #id= (SELECT last_sequence_value FROM table_name WHERE sequence_name = 'id') + 1
BEGIN
BEGIN TRANSACTION
BEGIN TRY
-- update SQL statement here
IF ##ROWCOUNT = 0
BEGIN
ROLLBACK TRANSACTION
RAISERROR('There was an error getting unique id in the table.',10,1)
RETURN
END
IF (##ERROR <> 0)
BEGIN
ROLLBACK TRANSACTION
RAISERROR('There was an error updating record to the table',10,1)
RETURN
END
END TRY
BEGIN CATCH
-- Raise an error with the details of the exception
RAISERROR(#ErrMsg, #ErrSeverity, 1) WITH SETERROR
END CATCH
COMMIT TRANSACTION
END
END
else
BEGIN
-- some sql select statements
END
END
First, compare
DECLARE #ErrorVar INT
RAISERROR(N'Message', 16, 1);
IF ##ROWCOUNT <> 0
PRINT 'Rows <> 0'
IF ##ERROR <> 0
PRINT N'Error = ' + CAST(##ERROR AS NVARCHAR(8));
GO
And this
DECLARE #ErrorVar INT,
#Error INT,
#Cnt INT
RAISERROR(N'Message', 16, 1);
SELECT #ERROR = ##ERROR, #Cnt = ##ROWCOUNT
IF #Cnt <> 0
PRINT 'Rows 0'
IF #ERROR <> 0
PRINT N'Error = ' + CAST(##ERROR AS NVARCHAR(8));
GO
https://learn.microsoft.com/en-us/sql/t-sql/functions/error-transact-sql
Because ##ERROR is cleared and reset on each statement executed, check it immediately following the statement being verified, or save it to a local variable that can be checked later. +

Anticipating fatal errors when using stored procedures

Please see the code below:
ALTER PROCEDURE GetPerson
AS
BEGIN TRANSACTION;
BEGIN TRY
DECLARE #TestVariable1 AS INT;
DECLARE #TestVariable2 AS INT;
SET #TestVariable1 = 1;
SET #TestVariable2 = 0;
DECLARE #TestVariable3 AS INT;
SET #TestVariable3 = #TestVariable1 / #TestVariable2;
PRINT 'transaction committed';
END TRY
BEGIN CATCH
ROLLBACK;
PRINT 'transaction rolled back';
END CATCH
I could run this using the command:
EXEC GetPerson
The transaction is successfully rolled back because you cannot divide my zero.
Now see the code below:
ALTER PROCEDURE GetPerson
AS
BEGIN TRANSACTION;
BEGIN TRY
CREATE TABLE #Test
(
test INT
);
SELECT TOP 1 *
FROM #Test;
SELECT TOP 1 *
FROM person.person;
DROP TABLE #Test;
SELECT TOP 1 *
FROM #Test;
PRINT 'transaction committed';
END TRY
BEGIN CATCH
ROLLBACK;
PRINT 'transaction rolled back';
END CATCH
If I run the same comment:
EXEC GetPerson
then I get an error:
Msg 208, Level 16, State 0, Procedure GetPerson, Line 11
Invalid object name '#Test'.
Msg 266, Level 16, State 2, Procedure GetPerson, Line 11
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1
I understand that not all errors are caught by the TRY clause as explained here: https://msdn.microsoft.com/en-gb/library/ms178592.aspx
When RAISERROR is run with a severity of 11 or higher in a TRY block, it transfers control to the associated CATCH block
My question is: how do you deal with these? I know in my code it is a clear developer mistake to try to select from a temp table that no longer exists. However, I am wandering if there are other errors I should deal with.
Is it acceptable practice to do this when calling the stored procedure:
EXECUTE GetPerson ;
IF ##trancount >= 1
ROLLBACK;
Alternatively I could do something like this:
EXEC GetPerson
IF ##trancount >= 1
INSERT INTO dbLog ("Trancount greater than 1 after running GetPerson")
Error 208 is a compilation error. Your code never run, anything, because it could not even compile. This is the same as asking a C# program to catch a compile error.
Besides, if you want a correct try/catch block that handles exceptions and transaction, see 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) ;
end catch
end
Obviously this will not handle syntax/binding errors (like 208), but at least will handle correct procedures. Remember that checking XACT_STATE() in the CATCH block is mandatory. Consider an error like 1205 (deadlock) where the CATCH block runs after the transaction was rolled back. You also need to honor and handle caller's transaction.

SQL Server transaction handling

I'm running the following stored procedure and I'm receiving the error
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
what am I missing here?
CREATE PROCEDURE spImportData
AS
BEGIN TRANSACTION
BEGIN TRY
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType ON
END TRY
BEGIN CATCH
PRINT 'IDENTITY_INSERT IS ON'
END CATCH
GO
BEGIN TRY
INSERT INTO PINCDOCControlNew..tblActionType(ActionTypeID,ActionType,ActionTypeDescription)
SELECT ActionTypeID,ActionType,ActionTypeDescription
FROM PINCDOCControlOld..tblActionType
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType OFF
END TRY
BEGIN CATCH
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType OFF
EXECUTE usp_GetErrorInfo
END CATCH
BEGIN TRY
SET IDENTITY_INSERT PINCDOCControlNew..tblArea ON
END TRY
BEGIN CATCH
PRINT 'IDENTITY_INSERT IS ON'
END CATCH
GO
BEGIN TRY
INSERT INTO PINCDOCControlNew..tblArea(AreaID,AreaDescription,AreaNo)
SELECT AreaNo,AreaDescription,Area
FROM PINCDOCControlOld..tblArea
SET IDENTITY_INSERT PINCDOCControlNew..tblArea OFF
END TRY
BEGIN CATCH
SET IDENTITY_INSERT PINCDOCControlNew..tblArea OFF
EXECUTE usp_GetErrorInfo
END CATCH
IF ##ERROR <> 0
BEGIN
-- Rollback the transaction
ROLLBACK
-- Raise an error and return
RAISERROR ('Error in inserting.', 16, 1)
RETURN
END
COMMIT
I would try to have just a single BEGIN TRY .... END TRY block, in which you have all your logic that you want to execute. If anything goes wrong - anywhere in your logic - you'll be thrown into the BEGIN CATCH.... END CATCH block.
Start your transaction before your BEGIN TRY, and have the only COMMIT as the last statement in your TRY block - and in your CATCH block, have a rollback.
Something like this:
CREATE PROCEDURE dbo.spImportData
AS
BEGIN TRANSACTION
BEGIN TRY
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType ON
INSERT INTO
PINCDOCControlNew..tblActionType(ActionTypeID, ActionType, ActionTypeDescription)
SELECT
ActionTypeID, ActionType, ActionTypeDescription
FROM
PINCDOCControlOld..tblActionType
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType OFF
-- tblArea
SET IDENTITY_INSERT PINCDOCControlNew..tblArea ON
INSERT INTO
PINCDOCControlNew..tblArea(AreaID, AreaDescription, AreaNo)
SELECT
AreaNo, AreaDescription, Area
FROM
PINCDOCControlOld..tblArea
SET IDENTITY_INSERT PINCDOCControlNew..tblArea OFF
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType OFF
SET IDENTITY_INSERT PINCDOCControlNew..tblArea OFF
EXECUTE usp_GetErrorInfo
RAISERROR ('Error in inserting.', 16, 1)
END CATCH
With this approach, you have exactly ONE BEGIN TRANSACTION, and either one single corresponding COMMIT TRANSACTION, or a single corresponding ROLLBACK TRANSACTION
I typically also add this SELECT statement to my CATCH block to get the error message and error code of what went wrong:
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
what am i missing here?
You probably have a surplus rather than a deficit. You need to remove the GO statements.
The only sensible interpretation of your script is that it is all intended to be part of the spImportData stored procedure definition.
This ends at the first GO statement (used by client tools to delimit batches) and the remaining batches are executed immediately in auto commit transactions as no explicit BEGIN TRAN has been run. When the COMMIT statement is reached there is nothing to commit hence the error.

a PRINT 'Success' that screw up a begin trans/ begin try commit / end try?

So I just saw a weird behavior
In one script there is something like:
begin transaction
begin try
stuff
stuff
stuff
print 'commit'
commit transaction
end try
begin catch
print 'rollback'
print error_message()
rollback transaction
end catch
thing is when this script in run, I see the print commit message but it does not make the commit and lock the tables/rows/etc
I have to manually run a commit by selecting the line and run it.
but if I do this
begin transaction
begin try
stuff
stuff
stuff
commit transaction
print 'commit'
end try
begin catch
print error_message()
rollback transaction
print 'rollback'
end catch
(swapped the print and the commit)
it work fine.
anyone know why this would happen?
this works fine for me:
--create table t (rowid int) --create one time before running script
begin transaction
begin try
insert into t values (1)
print 'commit'
print XACT_STATE() --should be 1
commit transaction
print XACT_STATE() --should be 0
end try
begin catch
print ERROR_MESSAGE()
rollback transaction
print 'rollback'
end catch
select * from t
output
commit
1
0
rowid
-----------
1
Close your SSMS window, open a new window, and then run your 1st script again, I'll bet you had an open transaction the first time you ran it, so you needed that extra COMMIT.
EDIT after OP comment:
run this exact script in a new connection to each database:
BEGIN TRY create table t (rowid int) END TRY BEGIN CATCH END CATCH
print 'A - XACT_STATE()='+ISNULL(CONVERT(varchar(10),XACT_STATE()),'')+', ##TRANCOUNT='+ISNULL(CONVERT(varchar(10),##TRANCOUNT),'')
begin transaction
begin try
insert into t values (1)
print 'commit'
print 'B - XACT_STATE()='+ISNULL(CONVERT(varchar(10),XACT_STATE()),'')+', ##TRANCOUNT='+ISNULL(CONVERT(varchar(10),##TRANCOUNT),'')
commit transaction
print 'C - XACT_STATE()='+ISNULL(CONVERT(varchar(10),XACT_STATE()),'')+', ##TRANCOUNT='+ISNULL(CONVERT(varchar(10),##TRANCOUNT),'')
end try
begin catch
print ERROR_MESSAGE()
rollback transaction
print 'rollback'
end catch
print 'D - XACT_STATE()='+ISNULL(CONVERT(varchar(10),XACT_STATE()),'')+', ##TRANCOUNT='+ISNULL(CONVERT(varchar(10),##TRANCOUNT),'')
select * from t
you should get this:
A - XACT_STATE()=0, ##TRANCOUNT=0
(1 row(s) affected)
commit
B - XACT_STATE()=1, ##TRANCOUNT=1
C - XACT_STATE()=0, ##TRANCOUNT=0
D - XACT_STATE()=0, ##TRANCOUNT=0
rowid
-----------
1
(1 row(s) affected)