I am writing a script to commit and rollback if the code should fail. There are no errors with in the code and the code (I think) executes. The problem is I am not seeing output from the code and I do not know if I should be seeing the code or how to see the code.
The 'UPDATE LISTINS' code will run but itself.
DROP PROCEDURE IF EXISTS displaylistingtest;
DELIMITER //
CREATE PROCEDURE DISPLAYLISTINGTEST()
BEGIN
DECLARE sql_error TINYINT Default false;
DECLARE continue handler for sqlexception
SET sql_error = true;
START TRANSACTION;
UPDATE LISTINGS
SET LISTING_PRICE = LISTING_PRICE +
CASE
WHEN LISTING_PRICE < 500000 THEN (LISTING_PRICE*1.05)/100
WHEN LISTING_PRICE >= 500000 THEN (LISTING_PRICE*1.10)/100
ELSE 0
END;
IF sql_error = FALSE THEN
COMMIT;
SELECT 'the transaction was commited.';
ELSE
ROLLBACK;
SELECT 'the tranaction was rolled back.';
END IF;
CALL display_listing;
END //
I get an error
Custom error message. Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0. Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
My SQL Code is following
Procedure 1
CREATE PROCEDURE [dbo].[spProcedure1]
#Id int
WITH EXECUTE AS OWNER
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
EXEC spProcedure2 #Id
-- Code ommited, Next procedures execution
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
-- Code ommited, saving error to Log table
RAISERROR(#errorMessage, #errorSeverity, #errorState);
END CATCH
END
Procedure 2
CREATE PROCEDURE [dbo].[spProcedure2]
#Id int
WITH EXECUTE AS OWNER
BEGIN
BEGIN TRY
-- Check if record exists
IF NOT EXISTS(SELECT * FROM dbo.Table t WHERE t.Id = #Id)
BEGIN
-- stop procedure due to user error
RAISERROR('Custom error message.',16,1)
RETURN
END
-- Code ommited, DML operation
END TRY
BEGIN CATCH
-- Code ommited, saving error to Log table
RAISERROR(#errorMessage, #errorSeverity, #errorState);
END CATCH
END
I am not sure how to handle transaction in the Procedure 2 to obey this error ?
Suppose I have more than one SQL to change the data in database in a SP. I create a transaction at the beginning, then commit or roll back the transaction at the end. What I did is something like:
declare #HasError bit
BEGIN TRANSACTION
set #HasError = 0;
Insert into Table1....
if(##ERROR>0)
set #HasError = 1;
Insert into Table2....
if(##ERROR>0)
set #HasError = 1;
Insert into Table3....
if(##ERROR>0)
set #HasError = 1;
...
if #HasError = 1
Rollback;
else
Commit;
It's working fine. But need to capture error for each U/I/D sql. Is there anyway I can know if there is any error at the end for only one piece of code like
if(##ERROR>0)
set #HasError = 1;
if #HasError = 1
Rollback;
else
Commit;
No need to do so many error detection for each U/I/D sql?
Not sure I follow the question, but something like this might work for you
BEGIN TRAN
BEGIN TRY
Insert into Table1....
Insert into Table2....
Insert into Table3....
END TRY
BEGIN CATCH
ROLLBACK TRAN
RETURN
END CATCH
COMMIT TRAN
Can someone tell me if it is possible to call another procedure from within a procedure and if any part of either procedure fails, roll everything back?
If this is possible, can someone please show me a tiny example of how this would be implemented?
EDIT: Procedure "b" fails but procedure "a" still inserts a row into table "a". It's my understanding that if any part of the insert fails that everything (both inserts) is rolled back which is not happening here. The questions is why not?
Procedure "a"
BEGIN
DECLARE b INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLWARNING ROLLBACK;
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
INSERT INTO a(a)
VALUES(iA);
CALL b(iB,LAST_INSERT_ID(),#b);
SELECT #b INTO b;
IF b !=1 THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END
Procedure "b"
BEGIN
DECLARE b INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLWARNING ROLLBACK;
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
INSERT INTO b VALUES(iB,id);
SET b=1;
COMMIT;
END;
You will need to handle transactions in both procedures, but the proc that is calling the other, should check for the return value and rollback it's transactions based on that. Here is an example of the inner proc:
How to detect a rollback in MySQL stored procedure?
you would then check for p_return_code and do a rollback of the parent transaction.
EDIT:
What I think is happening is that inner SP COMMIT or ROLLBACK affect outer SP TRANSACTION. This code works for me, if inner SP fail it rolls back both insert statements. First call to ab() works, new user record gets inserted and new game record gets inserted, if we remove record from the games table and run ab() again, because user id already exists it rolls back games table insert:
create procedure ab()
BEGIN
START TRANSACTION;
INSERT INTO games (title) VALUES ('bad game');
CALL ba(#ret);
IF #ret!=0 THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END;
create procedure ba(OUT return_value tinyint unsigned)
BEGIN
DECLARE exit handler for sqlexception
BEGIN
set return_value = 1;
END;
INSERT INTO users (id) VALUES(1);
set return_value = 0;
END;
To test use call ab();
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