Intermediate commit along with Rollback - sql

I have save point issued to commit 300 records in C . However once a fatal comes(divide by 0) i would want to rollback the records processsed , as well as update one table for recording this fatal record. How can i do commit for this fatal table alone and rollback previous records.

If you use SQL server :
Use TRY - CATCH method : If fatal error occurred then it goes to catch block and rollback transactions and after that rollback update fatal record into one table.If fatal error not occurred transactions committed as usual.
CREATE PROCEDURE Procedure_Name
(
#Parameter1 Data_type,
#Parameter2 Data_type
)
AS
BEGIN TRY
--- your SQL statements
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
--After rollback fatal record SQL statements
INSERT (or) UPDATE your fatal record
END CATCH

Related

Write stored procedure so if one statement fails it should not effect the other?

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;

How to handle Transaction in Nested procedure in SQL server?

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.

Try catch in trigger not suppressing error

I have a trigger and I want to surround the dml statements in the trigger by a try catch block so that any exception that occur in the trigger does not throw any exceptions outside the trigger. But the error is not suppressed.
My trigger is :
ALTER TRIGGER [dbo].[Deal.OnInsertUpdateAddDealAuditDetails]
ON [dbo].[Deal]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
BEGIN TRY
--SOME DML STATEMENTS
select 1/0
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
The error output is:
Msg 3616, Level 16, State 1, Line 1
An error was raised during trigger execution. The batch has been aborted and the user transaction, if any, has been rolled back.
XACT_ABORT is implicitly ON inside triggers. It rolls back the current transaction when a Transact-SQL statement raises a run-time error.
You have to handle the exception in original Insert query to aboid throwing the error.
BEGIN TRY
INSERT INTO deal(col1,col2,..)
VALUES (val1,val2,..)
END TRY
BEGIN CATCH
SELECT Error_number() AS ErrorNumber,
Error_message() AS ErrorMessage;
END CATCH
Note : The inserted records will not be present in the table. If you want the inserted records to be present in table though the trigger failed then you may have to commit the transaction first inside the trigger

How to check when a transaction occurs

Does anyone knows the command to check when a transaction occurs?
BEGIN TRAN
BEGIN
--- Do stuff with #id
INSERT INTO tbl_1(id, col_1, col_2)
SELECT #id, #val_1, val_2,
..
.....
.........
END
IF (##ERROR <> 0)
BEGIN
ROLLBACK TRAN
END ELSE
BEGIN
COMMIT TRAN --> would like to know when this started or logged?
-- Thinking about adding "exe some_trans_log getDate(), 'start - web_sp #id'" here
EXEC web_sp #id --> this takes a while to execute
-- Thinking about adding exe some_trans_log getDate(), 'end - web_sp #id'
END
I don't think it's necessary to add logging inside of your transactions, but I could be wrong.
Your this approach is wrong, first of all ##ERROR function is populated as soon as an error occurs
and if there is any other statement being executed after the error occured ##ERROR is set to null.
To use ##ERROR function properly you have to store its value to a variable as soon as you hve executed the statement. But to anticipate where an error can occur and storing its value to a variable is kind of an over kill. and error might occur somewhere you havent anticpated.
We have TRY..CATCH blocks in sql server which makes this kind of execution very simple.
You execute your main code in try block and during code execution if an error is raised the control jumps to catch block, there you have Sql Server Error Functions to collect detailed information about the error.
I would use the following approach to write a code something like this....
BEGIN TRY
/* Do some obvious validation checks here */
-- Check 1
IF(Something is not true)
BEGIN
RAISERROR('Something has gone wrong', 16,1)
END
-- Check 2
IF(Something is not true)
BEGIN
RAISERROR('Something has gone wrong', 16,1)
END
/* once you have done your checks then open a transations*/
BEGIN TRANSACTION
INSERT INTO tbl_1(id, col_1, col_2)
SELECT #id, #val_1, val_2,
COMMIT TRANSACTION
END TRY
BEGIN CATCH
/* If the validation failed and an error was raised
control will jump to this catch block Transaction was
never BEGAN.
if all the validations passed and something went wrong
when transaction was open. then it will roll back the
open transaction.
*/
IF ##TRANCOUNT <> 0
BEGIN
ROLLBACK TRANSACTION
END
SELECT ERROR_LINE() AS [Error_Line],
ERROR_MESSAGE AS [ERROR_MESSAGE],
ERROR_NUMBER AS [ERROR_NUMBER]
/* Use Error Function to collect information about the error */
/* Do other stuff log information about error bla bla */
END CATCH

Nested transaction can't be rolled back

I have SQL Server 2008 and want to do such a transaction:
begin transaction oo;
......
begin try
save transaction xx;
alter table ....; -- this will fail
alter table ....;
alter table ....;
end try
begin catch
rollback transaction xx; -- error here
end catch;
......
commit transaction oo;
At rollback transaction xx;, I get the message
3931 The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
What am I doing wrong here?
Update To explain the scenario:
There is a big transaction "oo", which will change the table structures of the database from product version X to product version Y.
In the nested transactions, user-specific-tables should be tried to be changed (= inner transaction).
If an user-specific-table is somehow corrupted, the whole product-upgrade process should not be rolled back.
On the other hand, the user-specific-tables should not be upgraded if something else failed during the main product table upgrade (outer transaction).
Reference
you have to use this line inside CATCH block
ROLLBACK TRANSACTION;
which will rollback all transaction,
when you use this one in your above statement (posted in Q) then it will
give us error
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
for it you have to put this line in TRY block
COMMIT TRANSACTION oo;
then finally your statement like that
BEGIN TRANSACTION oo;
BEGIN TRY
SAVE TRANSACTION xx;
CREATE TABLE test (ID INT); -- this will fail from second time
SELECT 3;
COMMIT TRANSACTION oo;
END TRY
BEGIN catch
ROLLBACK TRANSACTION;
END CATCH;
UPDATE after comment
BEGIN TRY
BEGIN TRANSACTION xx1;
select 1; -- this will always success
COMMIT TRANSACTION xx1;
BEGIN TRANSACTION xx2;
CREATE TABLE test (id int); -- this will fail from second time
COMMIT TRANSACTION xx2;
BEGIN TRANSACTION xx3;
select 3; -- this will fail from second time
COMMIT TRANSACTION xx3;
END TRY
BEGIN catch
ROLLBACK TRANSACTION
END CATCH;