Begin Transaction ... Commit Transaction Issue - sql

i have a question regarding using Transaction. Consider this code:
declare #trans_name varchar(max) = 'Append'
begin tran #trans_name
insert into SIDB_Module
(module_name, module_description, modulelevel, parentid, issystem, iscurrent)
values
(#module_name, #module_description, #modulelevel, #parentid, #issystem, 1)
set #moduleid = SCOPE_IDENTITY()
declare #id int = OBJECT_ID('SIDB_Module')
exec usp_M_SIDB_TransactionInformation_App_Append
#moduleid, id, 'append' ,#createdby_userid
if ##ERROR <> 0
rollback tran #trans_name
commit tran #trans_name
does transaction still apply on this.. even the next insert query is on the other stored procedure??

Yes, the call to usp_M_SIDB_TransactionInformation_App_Append is part of the transaction
Note: your error handling is "old style" (using ##ERROR) from SQL Server 2000 and will generate errors (error 266) if the inner proc rolls back or commits.
For more, see Nested stored procedures containing TRY CATCH ROLLBACK pattern?

Related

Create SQL Server procedure in a transaction

I need to create two procedures in a SQL Server transaction. If failure, I need to rollback the create(s) and any other executed queries in this transaction. I know the create statement must be the first statement in query batch, but I need to know how handle the transaction with multiple batches.
BEGIN TRANSACTION
CREATE PROCEDURE [dbo].[SP_SP-1]
#id BIGINT
AS
BEGIN
SET NOCOUNT ON;
-- SQL statements
END
GO
CREATE PROCEDURE [dbo].[SP_SP-2]
#id BIGINT
AS
BEGIN
SET NOCOUNT ON;
-- SP-2 statements
END
GO
UPDATE Table
SET Value = '1.0.0.5'
COMMIT TRANSACTION / ROLLBACK TRANSACTION
Below is one method to execute multiple batches in a transaction. This uses a temp table to indicate if any batch erred and perform a final COMMIT or ROLLLBACK accordingly.
Another method is to encapsulate statements that must be in single-statement batch (CREATE PROCEDURE, CREATE VIEW, etc.) but that can get rather ugly when quotes within the literal text must be escaped.
CREATE TABLE #errors (error varchar(5));
GO
BEGIN TRANSACTION
GO
CREATE PROCEDURE [dbo].[USP_SP-1]
#id bigint
AS
BEGIN
SET NOCOUNT ON;
-- SP Statments
END;
GO
IF ##ERROR <> 0 INSERT INTO #errors VALUES('error');
GO
CREATE PROCEDURE [dbo].[USP_SP-2]
#id BIGINT
AS
BEGIN
SET NOCOUNT ON;
-- SP-2 Statments
END;
GO
IF ##ERROR <> 0 INSERT INTO #errors VALUES('error');
GO
UPDATE Table SET Value='1.0.0.5'
GO
IF ##ERROR <> 0 INSERT INTO #errors VALUES('error');
GO
IF EXISTS(SELECT 1 FROM #errors)
BEGIN
IF ##TRANCOUNT > 0 ROLLBACK;
END
ELSE
BEGIN
IF ##TRANCOUNT > 0 COMMIT;
END;
GO
IF OBJECT_ID(N'tempdb..#errors', 'U') IS NOT NULL
DROP TABLE #errors;
GO
I suggest you to study more about this subject in Handling Transactions in Nested SQL Server Stored Procedures.
From the beginning, your syntax is wrong. You cannot begin a transaction and then create a procedure, you need to do just the opposite:
CREATE PROCEDURE [dbo].[SP_SP-1]
#id bigint
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
SET NOCOUNT ON;
-- SP-2 Statments
Update Table set Value='1.0.0.5'
END TRY
BEGIN CATCH
--handle error and perform rollback
ROLLBACK
SELECT ERROR_NUMBER() AS ErrorNumber
SELECT ERROR_MESSAGE() AS ErrorMessage
END CATCH
END
It is best practice to use TRY and CATCH when attempting to perform update inside transaction scope.
Please read more and investigate using the link I provided to get a bigger picture.
To use Transaction, you need to know what is the meaning of transaction. It's meaning of 'Unit of work either in commit state or rollback state'.
So when you use transaction, you must know that where you declare and where you close. So you must start and end transaction in the parent procedure only than it will work as a unit of work i.e. whatever no of query execute of DML statement, it uses the same transaction.
I do not understand why your update statement outside of procedure and transaction portion too.
It should be (See my comments, you can use TRY Catch same as c sharp) :
Create PROCEDURE [dbo].[SP_SP-1]
#id bigint
AS
BEGIN
Begin Transaction
SET NOCOUNT ON;
-- SP Statments
Exec SP_SP-2 #id --here you can pass the parameter to another procedure, but do not use transaction in another procedure, other wise it will create another transaction
If ##Error > 0 than
Rollback
Else
Commit
End
END
GO
--Do not use transaction in another procedure, otherwise, it will create another transaction which has own rollback and commit and do not participate in the parent transaction
Create PROCEDURE [dbo].[SP_SP-2]
#id BIGINT
AS
BEGIN
SET NOCOUNT ON;
-- SP-2 Statments
END
GO
i find this solution to execute the procedure as string execution , it`s a workaround to execute what i want
Begin Try
Begin Transaction
EXEC ('
Create PROCEDURE [dbo].[SP_1]
#id bigint
AS
BEGIN
SET NOCOUNT ON;
SP-1
END
GO
Create PROCEDURE [dbo].[SP_Inc_Discovery_RunDoc]
#id bigint
AS
BEGIN
SET NOCOUNT ON;
Sp-2
END')
Update Table set Value='1.0.0.5'
Commit
End Try
Begin Catch
Rollback
Declare #Msg nvarchar(max)
Select #Msg=Error_Message();
RaisError('Error Occured: %s', 20, 101,#Msg) With Log;
End Catch

Is rollback required in SQL Server when transaction failed in commit tran statement

I am working in some existing application using SQL Server 2014 in the backend. I find that the pattern to commit the transaction is like
USE AdventureWorks;
GO
BEGIN TRANSACTION;
GO
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 10;
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 11;
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 12;
GO
COMMIT TRANSACTION;
GO
I am wondering if the query failed in commit transaction statement, do i need to have the rollback statement there?
according to this question Can a COMMIT statement (in SQL) ever fail? How?, the commit tran can fail, but do I have to roll that back since the transaction hasn't been commit successfully. Would SQL server roll that back automatically when the connection is closed?
Please point me to the documentation in MSDN or wherever you got the information.
I believe it will, after the connection is closed. You should not count on this, there are many factors to consider including connection pooling. I suggest you look into SET XACT_ABORT ON and / or using a try catch block.
The way that I use transactions:
Begin Try
Begin Tran
-- do some work here...
Commit Tran
End Try
Begin Catch
If ( ##TranCount > 0 )
Rollback Tran
End Catch
doing your transaction commit/rollbacks in a try/catch is probably a best practice.
if, however you want your code to automatically rollback all of the statements in the transaction you need to add the "set xact_abort on" statement somewhere before the begin trans statement. xact_abort automatically rolls back all of the statements in a transaction if any of them fail. to understand the effect of xact_abort, execute the following code. set xact_abort on and off and observe the contents of the table. the first statement of the batch in the sample will always fail because of a primary key violation.
use tempdb
go
if exists (select * from sys.tables where name='t') drop table t
go
create table t (id int not null primary key)
go
insert t values(1)
go
set xact_abort on
begin transaction
insert t values(1)
insert t values(2)
commit transaction
go
select * from t
You can use try...catch like the below.
BEGIN TRY
DELETE
FROM HumanResources.JobCandidate
WHERE JobCandidateID = 10;
DELETE
FROM HumanResources.JobCandidate
WHERE JobCandidateID = 11;
DELETE
FROM HumanResources.JobCandidate
WHERE JobCandidateID = 12;
COMMIT;
END TRY
BEGIN CATCH
ROLLBACK
SELECT Db_name()
,CONVERT(NVARCHAR(15), Error_number())
,CONVERT(NVARCHAR(10), Error_line())
,Error_message()
END CATCH

Managing transaction in stored procedure with Dynamic SQL

I have a stored procedure with Dynamic SQL. Is it possible to include a batch of dynamic SQL inside an explicit transaction with COMMIT or ROLLBACK depending on the value of ##ERROR?
Following similar stored procedure. It is simplified in order to demonstration purpose.
CREATE PROCEDURE [dbo].[sp_Example]
AS
BEGIN
BEGIN TRANSACTION
DECLARE #ID VARCHAR(10)
INSERT INTO [dbo].[Deparment] (Name,Location,PhoneNumber) VALUES ('DeparmentName','DeparmentLocation','0112232332')
SELECT #ID =SCOPE_IDENTITY()
IF ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in Inserting Deparment.', 16, 1)
RETURN
END
SET #InsertQuery = '
DECLARE #Name varchar(100)
SELECT #Name = Name
FROM dbo.[Deparment]
WHERE DepartmentId= ''' + #ID +'''
INSERT INTO [dbo].[Employee](Name,Age,Salary,DepartmentName)VALUES(''EMPLOYEE NAME'',''25'',''200000'','''+#NAME'')''
EXEC(#InsertQuery)
IF ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in Inserting Employee.', 16, 1)
RETURN
END
COMMIT
END
Does outer Transaction scope applies to Dynamic query ?
The "outer" transaction will apply to everything that is executed. There is no way in SQL Server to not execute under the running transaction (which can be annoying of you want to log errors).

stored procedure sql server 2008

I am working on a project and am using stored procedure. I'm getting this error:
Line: 939
Error: Sys.WebForms.PageRequestManagerServerErrorException: Invalid object name 'IT_Assets'.
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
Please find below my stored procedure code:
alter PROCEDURE [ITAssets_sp_IT_Assets]
-- Add the parameters for the stored procedure here
(#Mode varchar(12)='ADD',
#ID integer , #AssetCode nvarchar(20)=null, #Description nvarchar(70)=null,
#Site nvarchar(10)=null)
AS
Begin
IF #Mode='ADD'
Begin
Begin Tran
INSERT INTO [IT_Assets]
([ID]
,[AssetCode]
,[Description]
,[Site])
values
(#ID, #AssetCode, #Description, #Site
)
If ##ERROR <> 0
ROLLBACK TRAN
Else
COMMIT TRAN
Select #ID
End
ELSE
Begin
Begin Tran
UPDATE [IT_Assets]
SET
AssetCode = #AssetCode, Description = #Description, Site = #Site
WHERE ID = #ID
If ##ERROR <> 0
ROLLBACK TRAN
Else
COMMIT TRAN
Select #ID
End
End
I didn't understand the error and I don't know exactly where is the problem? Would someone please help me in sloving this problem?
From the error Invalid object name 'IT_Assets' I believe that the table/view 'IT_Assets' is present in a diferent database than the stored procedure (assuming that the object exist and you are using the correct name).
Then you need to fully quaify it wih Db name like
UPDATE [DB_NAME].[dbo].[IT_Assets] (assuming `dbo` is the owner)
Try using your database name at top of procedure using use statement like
use [DB_NAME]
GO
alter PROCEDURE [ITAssets_sp_IT_Assets]
-- Add the parameters for the stored procedure here
(#Mode varchar(12)='ADD',
...
Also change you transaction handling using TRY .. CATCH consruct like below
Begin Tran
BEGIN TRY
INSERT INTO [IT_Assets]
([ID]
,[AssetCode]
,[Description]
,[Site])
values
(#ID, #AssetCode, #Description, #Site);
COMMIT TRAN;
END TRY
BEGIN CATCH
ROLLBACK TRAN ;
END CATCH

Commiting only Specific Changes made inside a TRANSACTION which may ROLLBACK

This is a significant edit from the original question, making it more concise and covering the points raised by existing answers...
Is it possible to have mulitple changes made to multiple tables, inside a single transaction, and rollback only some of the changes?
In the TSQL below, I would NOT want any of the changes made by "myLogSP" to ever be rolled back. But all changes made by the various myBusinessSPs should rollback if necessary.
BEGIN TRANSACTION
EXEC myLogSP
EXEC #err = myBusinessSPa
IF (#err <> 0) BEGIN ROLLBACK TRANSACTION RETURN -1 END
EXEC myLogSP
EXEC #err = myBusinessSPb
IF (#err <> 0) BEGIN ROLLBACK TRANSACTION RETURN -1 END
EXEC myLogSP
EXEC #err = myBusinessSPc
IF (#err <> 0) BEGIN ROLLBACK TRANSACTION RETURN -1 END
EXEC myLogSP
COMMIT TRANSACTION
RETURN 0
The order is important, the myLogSPs must happen between and after the myBusinessSPs (the myLogSPs pick up on the changes made by the myBusinessSPs)
It is also important that all the myBusinessSPs happen inside one transaction to maintain database integrity, and allow all their changes to rollback if necessary.
It's as if I want the myLogSPs to behave as if they're not part of the transaction. It is just an inconvenient fact that they happen to be inside one (by virtue of needing to be called between the myBusinessSPs.)
EDIT:
Final answer is "no", the only option is to redesign the code. Either to using table variables for the logging (as variables don't get rolled back) or redesign the business logic to Not require Transactions...
Use SAVEPOINTs, e.g.
BEGIN TRANSACTION
EXEC myLogSP
SAVE TRANSACTION savepointA
EXEC #err = myBusinessSPa
IF (#err <> 0) BEGIN
ROLLBACK TRANSACTION savepointA
COMMIT
RETURN -1
END
EXEC myLogSP
SAVE TRANSACTION savepointB
EXEC #err = myBusinessSPb
IF (#err <> 0) BEGIN
ROLLBACK TRANSACTION savepointB
COMMIT
RETURN -1
END
EXEC myLogSP
SAVE TRANSACTION savepointC
EXEC #err = myBusinessSPc
IF (#err <> 0) BEGIN
ROLLBACK TRANSACTION savepointC
COMMIT
RETURN -1
END
EXEC myLogSP
COMMIT TRANSACTION
EDIT
Based on the information provided so far (and my understanding of it) it appears that you will have to re-engineer you logging SPs, either to use variables, or to use files, or to allow them to run 'after the fact' as follows:
BEGIN TRANSACTION
SAVE TRANSACTION savepointA
EXEC #err = myBusinessSPa
IF (#err <> 0) BEGIN
ROLLBACK TRANSACTION savepointA
EXEC myLogSPA -- the call to myBusinessSPa was attempted/failed
COMMIT
RETURN -1
END
SAVE TRANSACTION savepointB
EXEC #err = myBusinessSPb
IF (#err <> 0) BEGIN
ROLLBACK TRANSACTION savepointB
EXEC myLogSPA -- the call to myBusinessSPa originally succeeded
EXEC myLogSPB -- the call to myBusinessSPb was attempted/failed
COMMIT
RETURN -1
END
SAVE TRANSACTION savepointC
EXEC #err = myBusinessSPc
IF (#err <> 0) BEGIN
ROLLBACK TRANSACTION savepointC
EXEC myLogSPA -- the call to myBusinessSPa originally succeeded
EXEC myLogSPB -- the call to myBusinessSPb originally succeeded
EXEC myLogSPC -- the call to myBusinessSPc was attempted/failed
COMMIT
RETURN -1
END
EXEC myLogSPA -- the call to myBusinessSPa succeeded
EXEC myLogSPB -- the call to myBusinessSPb succeeded
EXEC myLogSPC -- the call to myBusinessSPc succeeded
COMMIT TRANSACTION
You need to basically jump outside of the current context. There are a couple of ways to do that. One (which I have never tried) is to call the CLR to do the insert.
Perhaps a better way though is using the fact that table variables are not affected by transaction. For example:
CREATE TABLE dbo.Test_Transactions
(
my_string VARCHAR(20) NOT NULL
)
GO
DECLARE
#tbl TABLE (my_string VARCHAR(20) NOT NULL)
BEGIN TRANSACTION
INSERT INTO dbo.Test_Transactions (my_string) VALUES ('test point one')
INSERT INTO #tbl (my_string) VALUES ('test point two')
INSERT INTO dbo.Test_Transactions (my_string) VALUES ('test point three')
ROLLBACK TRANSACTION
INSERT INTO dbo.Test_Transactions (my_string) select my_string from #tbl
SELECT * FROM dbo.Test_Transactions
SELECT * FROM #tbl
GO
We have had luck with putting the log entries into table variables and then inserting to the real tables after the commit or rollback.
OK if you aren't on SQL Server 2008, then try this method. It's messy and a workaround but it should work. The #temp table and the table variable would have to be set up with the structure of what is returned by the sp.
create table #templog (fie1d1 int, field2 varchar(10))
declare #templog table (fie1d1 int, field2 varchar(10))
BEGIN TRANSACTION
insert into #templog
Exec my_proc
insert into #templog (fie1d1, field2)
select t.* from #templog t
left join #templog t2 on t.fie1d1 = t2.fie1d1 where t2.fie1d1 is null
insert into templog
values (1, 'test')
rollback tran
select * from #templog
select * from templog
select * from #templog
Use SAVEPOINTS and TRANSACTION ISOLATION LEVELS.
Wouldn't the easy way be to move the log insertion outside the transaction?
I don't really have an answer for you for the table lock, I think you already have the answer, there will have to be a table lock because the identity column may roll back.
move the BEGIN TRANSACTION statement to after the first insert.
Perhaps you could put the inserts/updates to the business tables in their own atomic transaction t1 and wrap each of these transactions in another transaction t2 that executes the log table update and t1 (the business table updates) without any rollbacks. For example:
BEGIN TRANSACTION t2
<insert to log>
<execute stored procedure p1>
END TRANSACTION t2
CREATE PROCEDURE p1
AS
BEGIN TRANSACTION t1
<insert to business tables>
<rollback t1 on error>
END TRANSACTION t1
I believe that when you rollback t1 in the stored procedure this will leave the calling transaction t2 unaffected.