I want to call a SP from another SP. I know I can easily call it. But Problem is, if an error occurs in SP2 then I want to ROLLBACK SP1.
SP1
BEGIN Tran
[Some Code]
Call to SP2
[Some Code]
SP2
BEGIN TRAN
[Some Code]
[Error Comes]
ROLLBACK TRAN
This would rollback Tran in sp2 only. I want to RollBack SP1 as well, if an error occurs in sp2.
Any help would be appreciated.
Try RAISERROR in SP2.
Seems people have issues with other information sites...
The gist of it is that the parent procedure will through an exception when trying to perform a ROLLBACK as the child already has. The way around this is to have the parent check the #trancount before committing.
http://forums.asp.net/t/1259708.aspx
Create procedure [dbo].[parent] as Begin Transaction Begin Try
Exec Child End Try Begin Catch
If ##Trancount > 0
RollBack End Catch Commit
Create procedure [dbo].[Child] as Begin Transaction Begin Try
--Do inserts here End Try Begin Catch
If ##Trancount > 0
RollBack
RAISERROR('Error Occured',16,1) End Catch Commit
One possibility is to create SP2 with an #ErrorCode INT OUTPUT parameter which indicates whether the caller needs to rollback or commit.
you can use an error code like that (i m not writing the code just how do i do if i were you)
SP1
DECLARE ReturnVal
BEGIN TRAN
CODE
CALL SP1 ReturnVal output
IF ReturnVal=errorvalue
ROLLBACK TRAN
SP2
DECLARE ReturnVal output
BEGIN TRAN
CODE
ERROR
SET ReturnVal=errorVal
ROLLBACK
RETURN ReturnVal
It doesn't sound like you need the nested transactions. Try controlling the commit/rollback with try blocks (psuedocode):
begin try
begin trans
do stuff
call other sp
do more stuff
commit trans
end try
begin catch
rollback trans
do something here to report failure to app
end catch
If an error occurs anywhere within the try block, including withing the other sp, the control will pass to the catch block and rollback the transaction.
Create an output parameter in your second SP which is of type bit, indicating wether an error occured or not. Based on this, you can rollback SP 1.
Related
I would ask a basilar thing, I would use the native transaction stuffs to rollback a bunch of tables if some conditions are verified.
I really have to check how many updates results based of some stored procedure that gather data from tables populated by input reports, too less changes or too many changes means rollback because we will reject the reports. I would implements this feature with the native transaction of T-SQL, this is a pseudo of my idea:
CREATE PROCEDURE mytest
AS
BEGIN
BEGIN TRANSACTION
BEGIN TRY
EXEC foo
EXEC bar
EXEC baz
END TRY
IF true
COMMIT TRANSACTION
ELSE
ROLLBACK TRANSACTION
END
Of course, this is not working because of incorrect syntax near the keyword 'IF', maybe because it's not the right way to use transaction.
Fixed this I will think how to count the changes of a table, if need to use a temporary table or if I can use some SQL features. I'm search for docs or example but I'm, not finding anything.
Any hints?
Your pseudo-code is missing a catch block. Below is an example that includes the error handling and other improvements.
CREATE PROCEDURE mytest
AS
SET XACT_ABORT ON; --best practice with explict transactions in procs
BEGIN TRY
BEGIN TRAN;
EXEC foo;
EXEC bar;
EXEC baz;
IF (<your-validation-succeeded-condition-here>)
COMMIT;
ELSE
ROLLBACK;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH;
The problem, as I mention in the comment, isn't the IF...ELSE it's that you have no CATCH. A TRY...CATCH requires both a TRY and a CATCH; unlike an IF...ELSE which can only have an IF.
You likely, therefore, want something like this:
CREATE PROCEDURE dbo.MyTest AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION MyTransaction;
BEGIN TRY
EXEC FOO;
EXEC BAR;
EXEC BAZ;
IF {Your Boolean Expression}
COMMIT TRANSACTION MyTransaction;
ELSE
ROLLBACK TRANSACTION MyTransaction;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION MyTransaction;
THROW;
END CATCH;
END;
I have this procedure which basically insert data.
Begin Transaction
Insert into [dbo].Values
(
EQ
)
values
(
#EQ
)
End
--Set #STATUSRet= 'Created'
--Set #ErrorRet= ''
Commit Transaction
End Try
Begin Catch
Set #STATUSRet= 'Failed'
Set #ErrorRet= (Select ERROR_MESSAGE())
Rollback Transaction
End Catch
Now I want to add a piece of code that calls another database server and insert data into the table in that server i.e. remotely. That's ok I will do that but if that fails then that should not effect my current process of inserting the data as I have described above i.e. if the remote data insertion fails it should not effect the prior insert in any way and should return successfully to the calling application behaving like nothing happened.
The default method of controlling transactions is auto-commit:
Any single statement that changes data and executes by itself is
automatically an atomic transaction. Whether the change affects one
row or thousands of rows, it must complete successfully for each row
to be committed. You cannot manually rollback an auto-commit
transaction.
So, if the two inserts are not wrapped in explicit transaction this will be the behavior. If you have more code blocks, then you can use two separate explicit transactions blocks like this:
DECLARE #ExecuteSecondTransaction BIT = 0;
-- local database
BEGIN TRY
BEGIN TRANSACTION;
-- CODE BLOCK GOES HERE
SET #ExecuteSecondTransaction = 1;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
-- GET ERRORS DETAILS OR THROW ERROR
END CATCH;
-- remote database
IF #ExecuteSecondTransaction = 1
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
-- CODE BLOCK GOES HERE
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
-- GET ERRORS DETAILS OR THROW ERROR
END CATCH;
END;
I have 2 proc i.e. Proc1 and Proc2.
I am executing proc1 inside proc2. There are multiple DML operation in both procedure. output of proc1 is used in proc2 for DML operation.
if Error occurred in proc2 then
how to handle transaction in both proc for rollback all DML operation?
Should I write transaction in both proc?
We use a generic error handler procedure based on http://www.sommarskog.se/error_handling/Part1.html that we - when applicable - include in our (nested) transactions to ensure the chain is managed properly:
CREATE PROCEDURE [dbo].[sp_ErrorHandler](#caller VARCHAR(255))
AS BEGIN
SET NOCOUNT ON;
DECLARE #errmsg NVARCHAR(2048), #severity TINYINT, #state TINYINT, #errno INT, #lineno INT;
SELECT #errmsg=REPLACE(ERROR_MESSAGE(), 'DatabaseException: ', 'DatabaseException: '+QUOTENAME(#caller)+' --> ')
, #severity=ERROR_SEVERITY()
, #state=ERROR_STATE()
, #errno=ERROR_NUMBER()
, #lineno=ERROR_LINE();
IF #errmsg NOT LIKE 'DatabaseException%' BEGIN
SELECT #errmsg=N'DatabaseException: '+QUOTENAME(#caller)+N', Line '+LTRIM(STR(#lineno))+N', Error '+LTRIM(STR(#errno))+N': '+#errmsg;
END;
RAISERROR('%s', #severity, #state, #errmsg);
END;
(Compiled in the master database and marked as system procedure)
We use this error handler as follows. In the demo I have an outer proc and an inner proc both using a transaction.
CREATE PROCEDURE dbo.uspOuterProc
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
EXEC dbo.uspInnerProc;
PRINT 1;
COMMIT;
END TRY
BEGIN CATCH
IF ##trancount > 0
ROLLBACK TRANSACTION;
EXEC master.dbo.sp_ErrorHandler #caller = 'dbo.uspOuterProc';
END CATCH;
END;
GO
CREATE PROCEDURE dbo.uspInnerProc
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
PRINT 2;
SELECT 1 / 0;
PRINT 3;
COMMIT;
END TRY
BEGIN CATCH
IF ##trancount > 0
ROLLBACK TRANSACTION;
EXEC master.dbo.sp_ErrorHandler #caller = 'dbo.uspInnerProc';
END CATCH;
END;
GO
After you compile this and run:
EXEC dbo.uspOuterProc
You should get this result:
2
Msg 50000, Level 16, State 1, Procedure sp_ErrorHandler, Line 13 [Batch Start Line 48]
DatabaseException: [dbo.uspOuterProc] --> [dbo.uspInnerProc], Line 12, Error 8134: Divide by zero error encountered.
You can handle the transaction in the outer procedure ( in your case proc2). If any error will be occurred in proc1 it will be taken care of by proc2 transaction handler.
Am assuming that proc1 will not be called directly, it will be called inside the proc2.
There are 3 basic transaction handling statements (and a few advanced ones I'm not gonna mention):
BEGIN TRANSACTION: Will raise the ##TRANCOUNT session variable by 1. If it goes from 0 to 1 then this marks the start of a transaction. Any value higher than 1 will keep the same transaction ongoing.
COMMIT: Will lower the ##TRANCOUNT session variable by 1. If it goes from 1 to 0 then the transaction is marked as finished and will impact all changes done since it was first created.
ROLLBACK: Will decrease the ##TRANCOUNT session variable to 0 (whichever it's value was), as long as it was at least 1 or higher. This will close the transaction and revert all changes done since it was first created.
Nested transactions are a bunch of BEGIN TRANSACTION statements put together. The only point where the transaction gets fully commited and the changes are made permanent is when there is a COMMIT that lowers the transaction count from 1 to 0. That means you need one COMMIT for each BEGIN TRANSACTION you executed, like a pyramid.
Check the following example:
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 1
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 2
COMMIT TRANSACTION
SELECT ##TRANCOUNT -- 1 (no change is permanent yet, not even the last one)
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 2
ROLLBACK
SELECT ##TRANCOUNT -- 0 (all changes were discarded)
When you have an SP that executes another SP and both have their transactions, the only thing you need to care about is to CATCH errors and do the proper ROLLBACK IF there's an open/active transaction ongoing (if not the ROLLBACK statement will fail saying that there is nothing to rollback).
A very basic CATCH would be like the following:
BEGIN TRY
BEGIN TRANSACTION
/* Do some operations */
/* Execute another SP that might have the following:
BEGIN TRANSACTION
-- Some other operations
COMMIT
*/
COMMIT
END TRY
BEGIN CATCH
DECLARE #v_ErrorMessage VARCHAR(MAX) = ERROR_MESSAGE()
IF ##TRANCOUNT > 0 -- Only rollback if there is an active transaction
ROLLBACK
RAISERROR (#v_ErrorMessage, 16, 1)
END CATCH
You can read this post if you want to delve deeply into the best way for handling transactions on SQL Server.
I have a query that checks data up front coming from the application to make sure the application is not passing bad data. If there is a piece of bad data a THROW is used to raise an error. Is this keeping my transactions open because I am not hitting the catch block then? If so, how would I handle this?
For Example:
BEGIN TRY
BEGIN TRANSACTION
IF NOT EXISTS(SELECT 1 FROM dbo.lutCode WHERE ID = #CodeID)
BEGIN
THROW('50001','Test Error',1)
END
UPDATE emp
SET emp.CodeID = #CodeID
WHERE emp.ID = #EmployeeID
IF XACT_STATE() = 1 COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() = -1 ROLLBACK TRANSACTION
THROW;
END CATCH
The query is a lot more complex than this, and in the real query the IF EXISTS does need to be in the transaction, so putting it outside of the transaction is not an option.
I would handle this a little differently ,
Do not open a transaction until you have done your validations, If validation fails raise an error in the try block and control will jump to catch block ignoring/skipping rest of the code in try block.
I have added the check IF(##TRANCOUNT <> 0) ROLLBACK TRAN because error maybe raised during validation and in that case a transaction will never be opened.
Only if something goes wrong while the update statement is being executed the control will jump to catch block without committing the transaction and there it will be rolled back and rest of the error logging stuff will be executed.
BEGIN TRY
-- do validatiion before openning transaction
IF NOT EXISTS(SELECT 1 FROM dbo.lutCode WHERE ID = #CodeID)
BEGIN
RAISERROR('Test Error',16, 1)
END
-- if test passed now open transaction
BEGIN TRANSACTION;
UPDATE emp
SET emp.CodeID = #CodeID
WHERE emp.ID = #EmployeeID
-- commit transaction if nothing gone wrong
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Rollback transaction if something went wrong
-- after you opened the trasaction
IF (##TRANCOUNT <> 0)
BEGIN
ROLLBACK TRANSACTION;
END
-- Other error logging
SELECT ERROR_LINE() AS Errorline
,ERROR_MESSAGE() AS ErrorMessage
,ERROR_NUMBER() AS ErrorNumber .......
END CATCH
change the statement in the catch block to this:
if xact_state() = 1 rollback tran
-1 only occurs in very specific circumstances, which I dont think you're getting in your query. If you change it to rollback on any xact_state which is not 0, you should stop getting hung transactions.
Also, I don't think you are supposed to use THROW in the TRY block; just the catch block. You'd want to use raiserror() to trigger an error in the try block, and then if you choose, use throw in the catch block.
I've got a stored procedure which begins a new transaction for data manipulation. The procedure itself is executed within another transaction.
I have no influence what happens before my procedure. And it could change.
My idea is to check the ##TRANCOUNT before I begin the nested transaction. Then check the ##TRANCOUNT again in catch block and compare it. In no case I want the outer transaction to be rollbacked. So i wonder if i am safe with this code?
thx for your help!
SET #TRANSCOUNTBEFORE = ##TRANCOUNT;
BEGIN TRANSACTION tx;
BEGIN TRY
/* some data manipulation here */
COMMIT TRANSACTION tx;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > #TRANSCOUNTBEFORE ROLLBACK TRANSACTION tx;
/* some error handling here */
END CATCH;