I am trying to implement a ROLLBACK functionality on my stored procedure that executes 2 UPDATE queries. I want the second update to only run if the first update was successful. Also if the second update fails, there should be a rollback of the first update statement. I did the below but I need to confirm from you guys if what I wrote is okay. Here is my stored procedure
BEGIN
declare exit handler for sqlexception
BEGIN
rollback;
END;
declare exit handler for sqlwarning
BEGIN
rollback;
END;
START transaction;
FIRST UPDATE
SECOND UPDATE
COMMIT;
SELECT FOUND_ROWS() INTO res;
END
Below is the drop and create script for my stored procedure.While executing this script on Database i'm getting error
Msg 102, Level 15, State 1, Line 18
Incorrect syntax near 'Date'.
Msg 105, Level 15, State 1, Line 35
Unclosed quotation mark after the character string '
END
'.
i tried with adding-removing quotation marks but did not worked.
can anyone tell what i have to do in order to execute the script successfully
/****** Object: StoredProcedure [dbo].[MY_PROC] Script Date: 9/12/2013 3:31:17 PM ******/
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MY_PROC]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[MY_PROC]
GO
/****** Object: StoredProcedure [dbo].[MY_PROC] Script Date: 9/12/2013 3:31:17 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MY_PROC]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql #statement = N'CREATE PROCEDURE [dbo].[MY_PROC]
(#P_Date DateTime=null)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #M_DATE_PARAM DATETIME
DECLARE #M_PREVIOUS_DATE DATETIME
DECLARE #M_TODAYS_DATE DATETIME
IF #P_Date IS NULL
SET #P_Date=GetDate()
PRINT 'Date passed as a parameter -' + CAST(#P_Date AS varchar(100))
BEGIN TRY
BEGIN TRANSACTION REIMBURSEMENT_TRN
BEGIN
-- my statements go here which do not cause any error
END
PRINT 'SUCCESS: Procedure Success'
COMMIT TRANSACTION ;
END TRY
BEGIN CATCH
PRINT 'ERROR: Procedure Fail'
PRINT ERROR_MESSAGE()
PRINT ERROR_LINE()
ROLLBACK TRANSACTION ;
END CATCH
PRINT 'Procedure Complete'
END
You should escape single quotation with another quotation
try this one :
PRINT ''Date passed as a parameter -'' + CAST(#P_Date AS varchar(100))
EDIT
By the way you should put an END at the end of your query
Complete code :
/****** Object: StoredProcedure [dbo].[MY_PROC] Script Date: 9/12/2013 3:31:17 PM ******/
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MY_PROC]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[MY_PROC]
GO
/****** Object: StoredProcedure [dbo].[MY_PROC] Script Date: 9/12/2013 3:31:17 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MY_PROC]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql N'CREATE PROCEDURE [dbo].[MY_PROC]
(#P_Date DateTime=null)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #M_DATE_PARAM DATETIME
DECLARE #M_PREVIOUS_DATE DATETIME
DECLARE #M_TODAYS_DATE DATETIME
IF #P_Date IS NULL
SET #P_Date=GetDate()
PRINT ''Date passed as a parameter -'' + CAST(#P_Date AS varchar(100))
BEGIN TRY
BEGIN TRANSACTION REIMBURSEMENT_TRN
BEGIN
-- my statements go here which do not cause any error
END
PRINT ''SUCCESS: Procedure Success''
COMMIT TRANSACTION ;
END TRY
BEGIN CATCH
PRINT ''ERROR: Procedure Fail''
PRINT ERROR_MESSAGE()
PRINT ERROR_LINE()
ROLLBACK TRANSACTION ;
END CATCH
PRINT ''Procedure Complete''
END'
END
PRINT needs a character string or Unicode string constant
Try to use
PRINT N''Date passed as a parameter -''...
Also you've missed the final quote after END of procedure.
EXEC dbo.sp_executesql #statement = N'CREATE PROCEDURE [dbo].[MY_PROC]
(#P_Date DateTime=null)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #M_DATE_PARAM DATETIME
DECLARE #M_PREVIOUS_DATE DATETIME
DECLARE #M_TODAYS_DATE DATETIME
IF #P_Date IS NULL
SET #P_Date=GetDate()
PRINT N''Date passed as a parameter -'' + CAST(#P_Date AS varchar(100))
BEGIN TRY
BEGIN TRANSACTION REIMBURSEMENT_TRN
BEGIN
-- my statements go here which do not cause any error
END
PRINT N''SUCCESS: Procedure Success''
COMMIT TRANSACTION ;
END TRY
BEGIN CATCH
PRINT N''ERROR: Procedure Fail''
PRINT ERROR_MESSAGE()
PRINT ERROR_LINE()
ROLLBACK TRANSACTION ;
END CATCH
PRINT N''Procedure Complete''
END'
I have a main working procedure and an accompanying logging procedure. I'm looking for verification on what my assumptions are with sql server 2008. The desired result is that SP1 does whatever work it can do and calls SP2 along the way to log progress. SP2 work is committed regardless of whether or not SP1 encounters an error that would cause it's transaction to rollback.
Does what I have look safe? Do I need to name transaction in SP2 differently?
------------------------------------------------------- SP1 pseudocode
BEGIN TRY
BEGIN TRANSACTION
-- Do stuff
-- log progress
exec SP2( getutcdate(), 'source_is_sp1','message for progress');
-- Do more stuff
-- log progress
exec SP2( getutcdate(), 'source_is_sp1','message for progress');
-- etc
COMMIT TRANSACTION
RETURN 0
END TRY
BEGIN CATCH
-- error handling not important
ROLLBACK TRANSACTION
return #StepNumber -- not important
END CATCH
------------------------------------------------------- SP2
CREATE PROCEDURE [dbo].[SP2]
#Source as nvarchar(100),
#Timestamp as datetime,
#Message as nvarchar(4000)
AS
BEGIN
SET NOCOUNT ON;
IF #Timestamp is null
BEGIN
set #Timestamp = GETUTCDATE();
END
IF #Source IS NOT NULL AND #Message IS NOT NULL
BEGIN
BEGIN TRANSACTION;
INSERT INTO LOG(LOGTIMESTAMP, SOURCEVCH, MESSAGEVCH)
VALUES (#Timestamp, #Source, #Message);
COMMIT TRANSACTION;
END
END
Lets say this is the situation:
[Stored Proc 1]
BEGIN
BEGIN TRANSACTION
...
exec sp 2
COMMIT
END
Now, if SP 2 - rolls back for whatever reason, does SP 1 - commit or rollback or throw exception?
Thanks.
It is possible for the work done by SP2 to be rolled back and not loose the work done by SP1. But for this to happen, you must write your stored procedures using a very specific pattern, as described in 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
Not all errors are recoverable, there are a number of error conditions that a transaction cannot recover from, the most obvious example being deadlock (your are notified of the deadlock exception after the transaction has already rolled back). Both SP1 and SP# have to be written using this pattern. If you have a rogue SP, or you want to simple leverage existing stored procedures that nilly-willy issue ROLLBACK statements then your cause is lost.
There are no autonomous transactions in SQL Server. You may see ##TRANCOUNT increase beyond 1, but a rollback affects the whole thing.
EDIT asked to point to documentation. Don't know of the topic that documents this explicitly, but I can show it to you in action.
USE tempdb;
GO
Inner proc:
CREATE PROCEDURE dbo.sp2
#trip BIT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
PRINT ##TRANCOUNT;
IF #trip = 1
BEGIN
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
END
ELSE
BEGIN
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
END
PRINT ##TRANCOUNT;
END
GO
Outer proc:
CREATE PROCEDURE dbo.sp1
#trip BIT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
PRINT ##TRANCOUNT;
BEGIN TRY
EXEC dbo.sp2 #trip = #trip;
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
END CATCH
PRINT ##TRANCOUNT;
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
PRINT ##TRANCOUNT;
END
GO
So now let's call it and let everything commit:
EXEC dbo.sp1 #trip = 0;
Results:
12110
Now let's call it and roll back the inner procedure:
EXEC dbo.sp1 #trip = 1;
Results:
120 <-- notice that a rollback here rolled back both
Transaction count after EXECUTE indicates a mismatching number
of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
00
If SP2 rolls back the transaction, SP1 will rollback as well.
See: http://msdn.microsoft.com/en-US/library/ms187844(v=sql.105).aspx for details.
In nested transactions, if any of the inner transations rolls back, all its outer transaction will rollback.
Here is a quick and dirty way to nest transactions in stored procedures (using the code from Aaron's answer) that can be useful sometimes. It uses a default parameter to indicate to the inner procedure if it is a nested call, and returns a success/fail result to the outer procedure.
CREATE PROCEDURE dbo.sp2
#trip BIT,
#nested BIT = 0
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON
IF #nested = 0 BEGIN TRAN
PRINT ##TRANCOUNT
IF #trip = 1
BEGIN
IF #nested = 0 ROLLBACK
RETURN 1
END
ELSE
BEGIN
IF #nested = 0 COMMIT
END
PRINT ##TRANCOUNT
RETURN 0
END
GO
The outer procedure checks the success/fail an rolls back the transaction if appropriate.
CREATE PROCEDURE dbo.sp1
#trip BIT
AS
BEGIN
DECLARE #result INT
SET NOCOUNT, XACT_ABORT ON
BEGIN TRAN
PRINT ##TRANCOUNT
BEGIN TRY
EXEC #result = dbo.sp2 #trip = #trip, #nested = 1
IF #result <> 0
BEGIN
ROLLBACK
RETURN 1
END
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE()
END CATCH
PRINT ##TRANCOUNT
COMMIT
PRINT ##TRANCOUNT
RETURN 0
END
GO
Every stored procedure must end with the same transaction count with which it entered. If the count does not match, SQL Server will issue error 266, "Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing."
If a stored procedure does not initiate the outermost transaction, it should not issue a ROLLBACK.
If a nested procedure begin a new transaction; but if it detects the need to roll back and the ##TRANSACTION value is greater than 1, it raises an error, returns an error message to the caller via out parameter or return value, and issues a COMMIT instead of a ROLLBACK.
CREATE PROCEDURE [dbo].[Pinner]
-- Add the parameters for the stored procedure here
#ErrorMessage varchar(max) out
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
begin tran
begin try
throw 51000, 'error occured', 1
commit tran
set #ErrorMessage = ''
end try
begin catch
set #ErrorMessage = ERROR_MESSAGE();
if ##TRANCOUNT = 1
rollback tran
if ##TRANCOUNT > 1
commit tran
end catch
END
create PROCEDURE [dbo].[Pouter]
-- Add the parameters for the stored procedure here
#ErrorMessage varchar(max) out
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
begin tran
begin try
EXECUTE [dbo].[Pinner]
#ErrorMessage OUTPUT
if #ErrorMessage <> '' begin
throw 51000, #ErrorMessage, 1
end
commit tran
set #ErrorMessage = ''
end try
begin catch
set #ErrorMessage = ERROR_MESSAGE();
if ##TRANCOUNT = 1
rollback tran
if ##TRANCOUNT > 1
commit tran
end catch
END
DECLARE #ErrorMessage varchar(max)
EXEC [dbo].[Pouter]
#ErrorMessage = #ErrorMessage OUTPUT
SELECT #ErrorMessage as N'#ErrorMessage'
https://www.codemag.com/article/0305111/handling-sql-server-errors-in-nested-procedures
USE [DemoProject]
GO
/****** Object: StoredProcedure [dbo].[Customers_CRUD] Script Date: 11-Jan-17 2:57:38 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[Customers_CRUD]
#Action VARCHAR(10)
,#BId INT = NULL
,#Username VARCHAR(50) = NULL
,#Provincename VARCHAR(50) = NULL
,#Cityname VARCHAR(50) = NULL
,#Number VARCHAR(50) = NULL
,#Name VARCHAR(50) = NULL
,#ContentType VARCHAR(50) = NULL
,#Data VARBINARY(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON;
--SELECT
IF #Action = 'SELECT'
BEGIN
SELECT BId , Username,Provincename,Cityname,Number,Name,ContentType, Data
FROM tblbooking
END
--INSERT
IF #Action = 'INSERT'
BEGIN
INSERT INTO tblbooking(Username,Provincename,Cityname,Number,Name,ContentType, Data)
VALUES (#Username ,#Provincename ,#Cityname ,#Number ,#Name ,#ContentType ,#Data)
END
--UPDATE
IF #Action = 'UPDATE'
BEGIN
UPDATE tblbooking
SET Username = #Username,Provincename = #Provincename,Cityname = #Cityname,Number = #Number,Name = #Name,ContentType = #ContentType,Data = #Data
WHERE BId = #BId
END
--DELETE
IF #Action = 'DELETE'
BEGIN
DELETE FROM tblbooking
WHERE BId = #BId
END
END
GO
I have a huge script for creating tables and porting data from one server. So this sceipt basically has -
Create statements for tables.
Insert for porting the data to these newly created tables.
Create statements for stored procedures.
So I have this code but it does not work basically ##ERROR is always zero I think..
BEGIN TRANSACTION
--CREATES
--INSERTS
--STORED PROCEDURES CREATES
-- ON ERROR ROLLBACK ELSE COMMIT THE TRANSACTION
IF ##ERROR != 0
BEGIN
PRINT ##ERROR
PRINT 'ERROR IN SCRIPT'
ROLLBACK TRANSACTION
RETURN
END
ELSE
BEGIN
COMMIT TRANSACTION
PRINT 'COMMITTED SUCCESSFULLY'
END
GO
Can anyone help me write a transaction which will basically rollback on error and commit if everything is fine..Can I use RaiseError somehow here..
Don't use ##ERROR, use BEGIN TRY/BEGIN CATCH instead. See this article: Exception handling and nested transactions for a sample procedure:
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
As per http://msdn.microsoft.com/en-us/library/ms188790.aspx
##ERROR: Returns the error number for the last Transact-SQL statement executed.
You will have to check after each statement in order to perform the rollback and return.
Commit can be at the end.
HTH
Avoid direct references to '##ERROR'.
It's a flighty little thing that can be lost.
Declare #ErrorCode int;
... perform stuff ...
Set #ErrorCode = ##ERROR;
... other stuff ...
if #ErrorCode ......