I have one procedure with multiple internal procedures. The main procedure is with try-catch block and transaction in while expression. Each of the internal procedures has no transaction and no try-catch block.
My question is if procedure3 crashes with an error, will the whole transaction be rolled back and procedure1 and procedure2 will not have executed? In this case what will XACT_STATE() do?
It looks like that:
DECLARE #param int;
CREATE TABLE #table (id int);
INSERT INTO #table (id)
SELECT SubmissionDataID
FROM [sob].[SubmissionData]
WHERE [sob].[SubmissionData].SubmissionStatusID = 9
AND [sob].[SubmissionData].[IsLast] = 1;
WHILE EXISTS (SELECT TOP 1 id FROM #table)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
SELECT TOP 1 #param=id FROM #table
EXEC procedure1 (#param);
EXEC procedure2 (#param);
EXEC procedure3 (#param);
EXEC procedure4 (#param);
EXEC procedure5 (#param);
EXEC procedure6 (#param);
EXEC procedure7 (#param);
DELETE FROM #table
WHERE id = #param
COMMIT TRANSACTION
END TRY
BEGIN CATCH
DELETE FROM #table
WHERE id = #param;
SELECT
ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE();
-- Transaction uncommittable
IF (XACT_STATE()) = -1
BEGIN
ROLLBACK TRANSACTION
PRINT 'INFO: TRANSACTION has been ROLLBACKED!';
THROW;
END
-- Transaction committable
IF (XACT_STATE()) = 1
COMMIT TRANSACTION
END CATCH
END --end while
DROP TABLE #table;
END
/* ************ END OF PROCEDURE ************ */
I'm not sure how XACT_STATE() works.
Related
Taking a SQL programming(2012 version) class and totally stuck, program wont work no matter how many times I try. The requirements (Questions) as well as what I have so far are below. Below the dashed line is another proc I wrote for error handling. Please help me finish this...please!
/*Create a Stored Procedure that accepts StockName, NewOpenPrice, NewClosePrice.
a. If the Stock Name does not EXIST a new record should be added into the dbo.Stocks table
b. If the Stock Name does EXIST, the OpenPrice an ClosePrice will be updated with the newly inserted Prices.
c. Insert and Update statements should be built using a transaction (Repeatable Read Isolation Level)
d. A Try Catch Statement should be used for the Update and Insert statements. If there is an error, the dbo.error_handler Stored Procedure should be called.
*/
CREATE PROCEDURE spc_Stocks
#Name varchar(25), #NewOpenPrice money, #NewClosePrice money
as
BEGIN
CREATE TABLE dbo.Stocks (
StockID int IDENTITY(1,1),
StockName varchar(50),
OpenPrice money,
ClosePrice money )
INSERT INTO dbo.Stocks
SELECT 'Walmart',21.58,22.98 UNION
SELECT 'Target',17.32,15.23 UNION
SELECT 'Taco Bell',4.58,12.98 UNION
SELECT 'Microsoft',7.15,8.15 UNION
SELECT 'Apple',10.79,9.89
Select StockName from stocks
where StockName = #Name
------Name does NOT exist
if (#Name = NULL)
Begin
Insert into dbo.Stocks (StockName)
Values (#Name)
END
----If name DOES exist
ELSE
BEGIN
Begin TRY
Begin SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
UPDATE dbo.Stocks SET OpenPrice =#NewOpenPrice, ClosePrice= #NewClosePrice where StockName = #Name
Commit transaction
END TRY
BEGIN CATCH
?!?!?!?!?!?
*This is my ErrorHandler stored proc query
ALTER PROCEDURE dbo.error_handler
as
BEGIN
DECLARE #errnum INT,
#severity INT,
#errstate INT,
#proc NVARCHAR(126),
#line INT,
#message NVARCHAR(4000)
-- capture the error information that caused the CATCH block to be invoked
SELECT #errnum = ERROR_NUMBER(),
#severity = ERROR_SEVERITY(),
#errstate = ERROR_STATE(),
#proc = ERROR_PROCEDURE(),
#line = ERROR_LINE(),
#message = ERROR_MESSAGE()
end
Create PROCEDURE [dbo].[USP_Stocks]
(#Name varchar(25), #OpenPrice MONEY, #ClosePrice MONEY)
AS
BEGIN
-----Name does NOT exist
IF NOT EXISTS (SELECT StockName FROM [dbo].[Stocks]
WHERE StockName = #Name)
BEGIN
BEGIN TRY
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
INSERT INTO dbo.Stocks (StockName, OpenPrice, ClosePrice)
VALUES (#Name ,#OpenPrice, #ClosePrice)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
EXEC dbo.error_handler
ROLLBACK
END CATCH
END
----If name DOES exist
ELSE
BEGIN
BEGIN TRY
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
UPDATE dbo.Stocks SET OpenPrice =#OpenPrice, ClosePrice= #ClosePrice
where StockName = #Name
Commit transaction
END TRY
BEGIN CATCH
ROLLBACK TRUANSACTION
EXEC dbo.error_handler
END CATCH
END
END
I've a stored procedure where there are two insert statement for two different tables.
create procedure p_multiple_insert
(
// variable declaration
)
declare #temp int = 0;
begin try
begin
// insert statement for TABLE1
set #temp = 1;
if(#temp = 1)
begin
// insert statement for TABLE2
end
end
end try
begin catch
// insert into error table error_number() & error_message()
end catch
If the error occurs with first insert statement block, #temp = 0. So second insert block does not execute and data is also not inserted in the TABLE1.
But if the error occur while inserting into TABLE2, how can I rollback my whole transaction. I mean roll back first insert statment too.
my changes are in UPPER CASE:
create procedure p_multiple_insert
(
-- variable declaration
)
declare #temp int = 0;
BEGIN TRANSACTION
begin try
-- insert statement for TABLE1
set #temp = 1;
if(#temp = 1)
begin
-- insert statement for TABLE2
end
end
COMMIT TRANSACTION
end try
begin catch
ROLLBACK TRANSACTION
-- insert into error table error_number() & error_message()
end catch
BEGIN TRANSACTION at the beginning
COMMIT at the end of try block
ROLLBACK everything in catch-block
My "Standard-Catch-Block" always looks like this:
DECLARE #errmsg NVARCHAR(MAX)
SELECT #errmsg = 'Error executing dbo.StoredProcName: '
+ COALESCE(ERROR_MESSAGE(),'No Message from SQL Server')
RAISERROR(#errmsg,16,1)
ROLLBACK TRANSACTION
I have two stored procedures, one nested inside the other. When the nested stored procedure is called, at the moment, it should error with a foreign key constraint violation and then rollback the earlier call to insert into the ProductLicense table. The nested procedure does not perform any action on the database because of the foreign key violation but the calling stored procedure isn't catching the error and rolling back. If I execute the nested stored procedure by itself it does return error 547 Foreign key violation.
How can I get the two stored procedures to work together?
Outer procedure:
ALTER PROCEDURE [dbo].[AddNewLicense2_i]
-- Add the parameters for the stored procedure here
#customerId nvarchar(10),
#licenseModeId int,
#licenseModeProgramId int,
#createdBy int,
#updateBy int,
#systemId nvarchar(50),
#productId int
AS
BEGIN TRY
BEGIN TRANSACTION
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--SET XACT_ABORT ON; --used for automatic rollback when an error occurs
DECLARE #tempDays INT
DECLARE #programCornerAmt INT
DECLARE #tempEndDate DATETIME
DECLARE #tempExpDate DATETIME
DECLARE #err INT
SET #err = 0
/*SET #tempDays = (SELECT lmp.TimeoutDays
FROM LicenseModeProgram lmp
WHERE lmp.LicenseModeProgramId = #licenseModeProgramId)*/
SELECT #tempDays = TimeoutDays, #programCornerAmt = MonthlyCornersAmount
FROM LicenseModeProgram
WHERE LicenseModeProgramId = #licenseModeProgramId
--Build Expiration and End Dates.
IF #tempDays = NULL --then this is NOT a time rental or metered system
BEGIN
SET #tempEndDate = NULL
SET #tempExpDate = NULL
END
ELSE
BEGIN
SET #tempEndDate = DATEADD("d", #tempDays, GETDATE())
SET #tempExpDate = DATEADD("d", #tempDays, GETDATE())
END
-- Create new product license record
INSERT INTO ProductLicense (CustomerId, LicenseModeId, LicenseModeProgramId, CreatedBy, UpdatedBy, SystemId, ProductId, ExpirationDate, LicenseEndDate)
VALUES (#customerId, #licenseModeId, #licenseModeProgramId, #createdBy, #updateBy, #systemId, #productId, #tempExpDate, #tempEndDate)
IF #licenseModeId = 4 AND #systemId NULL AND #programCornerAmt NULL
--call stored procedure to add corners to the customer account
EXECUTE #err = AddMeteredTx_i #systemId, 1, 1, #programCornerAmt , 'Initial License Creation'
PRINT #err
COMMIT TRANSACTION
END TRY
BEGIN CATCH
RAISERROR('Failed to Create License', 11, 2)
ROLLBACK TRANSACTION
RETURN 1
END CATCH
--COMMIT TRANSACTION
RETURN 0
GO
Inner procedure:
ALTER PROCEDURE [dbo].[AddMeteredTx_i]
-- Add the parameters for the stored procedure here
#systemId nvarchar(50),
#activityEventId int,
#createdBy int,
#amount int,
#notes text
AS
BEGIN TRY
BEGIN TRANSACTION
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--SET XACT_ABORT ON; --used for automatic rollback when an error occurs
INSERT INTO CustomerAccountActivity (SystemId, ActivityEventId, CreatedBy, Amount, Notes)
VALUES (#systemId, #activityEventId, #createdBy, #amount, #notes)
UPDATE CustomerAccount
SET MeteredBalance = (SELECT MeteredBalance FROM CustomerAccount WHERE SystemId = #systemId) + #amount
WHERE SystemId = #systemId
COMMIT TRANSACTION
END TRY
BEGIN CATCH
RAISERROR('Error Update to Customer Account Record ', 11, 2)
ROLLBACK TRANSACTION
RETURN 1
--COMMIT TRANSACTION
END CATCH
RETURN 0
GO
Catching errors with a call stack like this using ##Error can be problematic. It's a lot more reliable to use TRY/CATCH
The basic format is:
BEGIN TRY
<BEGIN TRAN>
... do stuff ...
<COMMIT TRAN>
END TRY
BEGIN CATCH
<ROLLBACK TRAN>
... do error stuff like re-raise the error to outer scope ...
END CATCH
Any error encountered in the try will automatically take you to the CATCH block without additional checking.
I've tried putting the COMMIT TRAN in a if else loop, and I'm still getting this error.
I have to enroll a student in a class. If the number of seats after enrollment falls in negative, I have to reverse it and print a message saying can't enroll. I have put other error messages just to see how transactions work.
CREATE PROCEDURE dbo.EnrollStudent ( #CourseID AS INTEGER,
#StudentID AS VARCHAR(20) ) AS
BEGIN
DECLARE #StatusID INTEGER
DECLARE #Status VARCHAR(50)
DECLARE #CurrentSeats INTEGER
DECLARE #ErrorCode INTEGER
SET #StatusID=0
IF EXISTS (SELECT 1
FROM dbo.CourseEnrollment
WHERE dbo.CourseEnrollment.CourseId=#CourseID AND dbo.CourseEnrollment.StudentId=#StudentID )
BEGIN
BEGIN TRAN Tr1
SET #StatusID = 1
SELECT #ErrorCode=##ERROR
IF (#ErrorCode<>0) GOTO OTHERPROBLEM
ELSE
COMMIT TRAN Tr1
END
IF EXISTS ( SELECT 1
FROM dbo.CourseEnrollment
FULL OUTER JOIN dbo.Courses
ON dbo.Courses.CourseId=#CourseID
WHERE dbo.CourseEnrollment.StudentId<>#StudentID AND dbo.Courses.Faculty IS NULL )
BEGIN
BEGIN TRAN Tr2
SET #StatusID=2
SELECT #ErrorCode=##ERROR
IF (#ErrorCode<>0) GOTO OTHERPROBLEM2
ELSE
COMMIT TRAN Tr2
END
IF #StatusID=0
BEGIN
IF EXISTS ( SELECT 1
FROM dbo.Courses
WHERE dbo.Courses.CourseId=#CourseID AND dbo.Courses.Faculty IS NOT NULL )
BEGIN
BEGIN TRAN Tr3
SET #StatusID=3
BEGIN TRAN InsertingValues
INSERT INTO dbo.CourseEnrollment (dbo.CourseEnrollment.StudentId,dbo.CourseEnrollment.CourseId)
VALUES (#StudentID,#CourseID);
SELECT #ErrorCode=##ERROR
IF (#ErrorCode<>0) GOTO InsertProblem
ELSE
COMMIT TRAN InsertingValues
BEGIN TRAN UpdateCourses
UPDATE dbo.Courses
SET OpenSeats = OpenSeats-1
WHERE dbo.Courses.CourseId = #CourseID
SELECT #ErrorCode=##ERROR
IF (#ErrorCode<>0) GOTO UpdateProblem
ELSE
COMMIT TRAN UpdateCourses
SELECT #CurrentSeats=OpenSeats
FROM dbo.Courses
WHERE dbo.Courses.CourseId = #CourseID
IF (#CurrentSeats<0) GOTO PROBLEM
ELSE
COMMIT TRAN Tr3
END
END
OTHERPROBLEM:
BEGIN
PRINT 'Unable to set status'
ROLLBACK TRAN
END
OTHERPROBLEM2:
BEGIN
PRINT 'Unable to set status'
ROLLBACK TRAN
END
UpdateProblem:
BEGIN
PRINT 'Not able to update values'
ROLLBACK TRAN InsertingValues
END
InsertProblem:
BEGIN
PRINT 'Not able to insert'
ROLLBACK TRAN InsertingValues
END
PROBLEM:
BEGIN
PRINT 'Seats Full!'
ROLLBACK TRAN
END
IF #StatusID = 1
BEGIN
SET #Status = 'The Student is already enrolled'
END;
ELSE IF #StatusID = 2
BEGIN
SET #Status = 'Cannot enroll until faculty is selected'
END
ELSE IF #StatusID = 3
BEGIN
SET #Status = 'Student Enrolled'
END
SELECT #Status
END;
This correctly updated the tables, but is giving the following errors:
(1 row(s) affected)
(1 row(s) affected)
Unable to set status
Msg 3903, Level 16, State 1, Procedure EnrollStudent, Line 101
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
Unable to set status
Msg 3903, Level 16, State 1, Procedure EnrollStudent, Line 108
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
Not able to update values
Msg 3903, Level 16, State 1, Procedure EnrollStudent, Line 115
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
Not able to insert
Msg 3903, Level 16, State 1, Procedure EnrollStudent, Line 123
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
Seats Full!
Msg 3903, Level 16, State 1, Procedure EnrollStudent, Line 131
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
(1 row(s) affected)
The error you are getting is because you are rolling back without having an open transaction (you have either already committed or rolled-back). Consider cleaning up the structure of your stored proc, try executing your entire stored proc as one transaction, and then rolling back if an error occurs. You can also test if a rollback is required by checking if a transaction is open:
BEGIN TRANSACTION;
BEGIN TRY
--execute all your stored proc code here and then commit
COMMIT;
END TRY
BEGIN CATCH
--if an exception occurs execute your rollback, also test that you have had some successful transactions
IF ##TRANCOUNT > 0 ROLLBACK;
END CATCH
You need to specify the transaction name you want to rollback if it is named.
begin with that.
After that you could tell us wich transaction is failing (ensure that the transaction it is not being commited before).
BEGIN TRAN Tr1
-- your code
ROLLBACK TRAN Tr1
In my case I was using pyodbc to access SQL Server. Even a simple "SELECT * from table" was resulting in an error like "ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION".
Turned out to be permission related.
Just from a quick look - could it be because you have named your transaction when you start it (Tr1), but not referred to the name in your error handler?
DECLARE #Error varchar(max)
SET #Error = ''
BEGIN TRY
INSERT INTO OPERACION(CONTRATOID,FLUJO,MONTO,ID_ORIGINAL,ID_TRX,ESTADO,TIPO,REFERENCIA,
US_CREA,US_ACT,FEC_CRE,REQUEST,RESPONSE)
VALUES(#P_CONTRATOID,#P_FLUJO,#P_MONTO,#P_ID_ORIGINAL,#P_ID_TRX,#P_ESTADO,
#P_TIPO,#P_REFERENCIA,#P_US_CREA,#P_US_ACT,getdate(),#P_REQUEST,#P_RESPONSE)
END TRY
BEGIN CATCH
SELECT #Error = 'err: '+ ERROR_MESSAGE()
ROLLBACK ;
END CATCH
SELECT #Error
IF #FailureCount = 0
BEGIN
IF ##TRANCOUNT > 0
BEGIN
COMMIT TRAN a
END
END
ELSE
BEGIN
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRAN a
END
END
I have this stored procedure, which works if I execute manually. But sometimes I see timeout exception on running this Stored Procedure. Is this is due to MERGE, http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/
ALTER PROCEDURE [dbo].[InsertOrUpdateMobileUser]
(
#ID BIGINT
,#Name NVARCHAR(255)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TranCount INT;
SET #TranCount = ##TRANCOUNT;
BEGIN TRY
IF #TranCount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION InsertOrUpdateMobileUser;
MERGE INTO MobileUsers MU
USING (SELECT #ID AS ID) T ON (MU.ID = T.ID)
WHEN MATCHED THEN
UPDATE SET [Name] = CASE WHEN #Name IS NULL OR #Name = '' THEN [Name] ELSE #Name END
WHEN NOT MATCHED THEN
INSERT ([Name])
VALUES (#Name)
SELECT *
FROM MobileUsers
WHERE ID = #ID;
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 InsertOrUpdateMobileUser;
RAISERROR ('InsertOrUpdateMobileUser: %d: %s', 16, 1, #error, #message) ;
END CATCH
END
You differentiate between two execution methods. What are they, exactly? You mean that it works if run only the code of the procedure, and doesn't work when you EXECUTE the proc? Of it works through EXECUTE, and fails in a job?
Timeouts only concern client applications. You have SqlCommand .CommandTimeout property in .NET, and in Management Studio there's Tools>Options>Query Execution>Command Timeout. If you have a job, then it should run infinitely, there's even no option to set the timeout in Sql Server Agent.