ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION - sql

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

Related

Result from job procedure with multiple internal procedures

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.

Running through all rows in cursor even if there are errors

I am trying to delete two Ids from a table, in which id '1' can't be deleted as there is another table referring to Users table but id '2' can be deleted so I want that even if it will give me runtime error for id '1' , it should run successfully for '2', deleting id '2' from table
But with this code I am getting this error
Msg 3998, Level 16, State 1, Line 1
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.
DECLARE #Id int
DECLARE TempCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT ID FROM Users
WHERE Id IN (1,2)
OPEN TempCursor
WHILE 1=1
BEGIN
FETCH NEXT FROM TempCursor
INTO #Id
IF ##FETCH_STATUS < 0 BREAK
BEGIN TRY
SET XACT_ABORT ON
BEGIN TRAN DeleteTrans
DELETE Users WHERE Id = #Id
COMMIT TRAN DeleteTrans
END TRY
BEGIN CATCH
print #AdviserBusinessId
END CATCH
END
CLOSE TempCursor ;
DEALLOCATE TempCursor ;

How to avoid concurrent read access issue in SQL Server?

I am getting a date value from a tblinfo table. If the date is null, then I will insert into table, else I will update the row in the table.
I am facing the issue that 2 sessions accessing the table simultaneously with same information, both are getting Null, so both are trying to insert records into same table with same primary key value. We are getting the primary key constraints issue. How to stop this?
begin tran
set #date=(select date from tblinfo where id=#id)
if(#date is null)
--insert
else
--update
commit tran
Using Locking
declare #Date DateTime
, #RowId bigint = 10
Begin Tran
select #Date = SomeDate
from tblInfo
with (RowLock) --this is the important point; during the transaction, this row will be locked, so other statements won't be able to read it
where Id = #RowId
if (#Date is Null)
begin
insert SomeTable (TblInfoId, Date)
values (#RowId, GetUtcDate())
end
else
begin
update SomeTable
set Date = #Date
where tblInfoId = #RowId
end
if ##TranCount > 0 Commit Tran
Technique Avoiding Locking
If preventing reads from occurring is an issue for some reason, the below is a lockless approach; but requires an extra field in your database.
declare #Date DateTime
, #RowId bigint = 10
, #DataAvailable bit
Begin Tran
update tblInfo
set session_lock = ##SPID
where Id = #RowId
and session_lock is null
select #Date = SomeDate
, #DataAvailable = 1
from tblInfo
where Id = #RowId
and session_lock = ##SPID
if (#DataAvailable = 1)
begin
if (#Date is Null)
begin
insert SomeTable (TblInfoId, Date)
values (#RowId, GetUtcDate())
end
else
begin
update SomeTable
set Date = #Date
where tblInfoId = #RowId
end
update tblInfo
set session_lock = null
where Id = #RowId
and session_lock = ##SPID
end
--optionally do something if the row was already "locked"
--else
--begin
--...
--end
if ##TranCount > 0 Commit Tran
Right after "begin tran", update your table like below script
begin tran
-- this update will lock the table so no other process can read data
update tblInfo with (tablock)
set date = date
---- do what ever you need to do here
set #date=(select * from tblinfo)
if(#date is null)
--insert
else
--update
commit tran
this will cause SQL Server lock the table and the second transaction will wait reading the data until the first process finish.
Relying on lock hints to get the desired outcome will perhaps prevent you from attempting to insert the value twice, but it won't prevent errors from happening. You'll just get an 'Unable to acquire lock' or deadlock error instead.
You should put a mutex in your code, in the minimally sized critical section. Using sp_getApplock, you can get a lock on a section of code with a wait period that you specify (subsequent threads will wait for the lock to clear and then continue). Here's some sample code:
declare #LockResource nvarchar(100) = 'VALUE_INSERT'
begin transaction
-- Fetch the lock:
EXEC #res = sp_getapplock
#Resource = #LockResource,
#LockMode = 'Exclusive',
#LockOwner = 'Transaction',
#LockTimeout = 10000,
#DbPrincipal = 'public'
if #res not in (0, 1)
begin
set #msg = 'Unable to acquire Lock for ' + #LockResource
raiserror (#msg , 16, 1)
rollback transaction
return
end
else
begin
begin try
---------------------------------------------
-- Fetch value if it exists, insert if not.
-- Both need to happen here.
---------------------------------------------
end try
begin catch
select #msg = 'An error occurred: ' + ERROR_MESSAGE()
-- Release the lock.
EXEC #res = sp_releaseapplock
#Resource = #LockResource,
#DbPrincipal = 'public',
#LockOwner = 'Transaction'
rollback transaction
raiserror(#msg, 11, 1)
goto cleanup
end catch
-- Release the lock.
EXEC #res = sp_releaseapplock
#Resource = #LockResource,
#DbPrincipal = 'public',
#LockOwner = 'Transaction'
end
commit transaction

Stored Procedure doesn't return affected rows after exception handling

I have added exception handling in my stored procedure as below.
ALTER PROCEDURE [dbo].[BUDGETUPDATE]
#DistrictID int
AS
BEGIN
SET NOCOUNT ON ;
BEGIN TRY
BEGIN TRAN
UPDATE bud
SET bud.BudgetStateID = #BudgetStateID
FROM [dbo].[BudgetOffice] bud
INNER JOIN [dbo].[vw_Office] vw
ON (vw.OfficeID = bud.OfficeID)
WHERE vw.DistrictID = #DistrictID
IF ##ERROR = 0
BEGIN
COMMIT TRAN;
SELECT ##ROWCOUNT AS AffectedRow;
END
END TRY
BEGIN CATCH
SELECT ##ERROR AS ERROR
ROLLBACK TRAN;
END CATCH
SET NOCOUNT OFF ;
END
I need to return the number of affected rows using ##ROWCOUNT. But this stored procedure always returns rowcount as 0. Any reason for this. Do I need to write the ##rowcount statement right after update?
You need to select ##ROWCOUNT after your UPDATE statement. As per the documentation:
Statements such as USE, SET , DEALLOCATE CURSOR, CLOSE CURSOR,
BEGIN TRANSACTION or COMMIT TRANSACTION reset the ROWCOUNT value to 0.
Since your ##ROWCOUNT is after the COMMIT TRAN, ##ROWCOUNT returns 0.
You need to store result of global variables in local variable because it will change after next instruction like:
ALTER PROCEDURE [dbo].[BUDGETUPDATE]
#DistrictID int
AS
BEGIN
SET NOCOUNT ON ;
DECLARE #rowcount INT, #error INT;
BEGIN TRY
BEGIN TRAN
UPDATE bud
SET bud.BudgetStateID = #BudgetStateID
FROM [dbo].[BudgetOffice] bud
JOIN [dbo].[vw_Office] vw
ON vw.OfficeID = bud.OfficeID
WHERE vw.DistrictID = #DistrictID;
SELECT #error = ##ERROR, #rowcount = ##ROWCOUNT;
IF #error = 0
BEGIN
COMMIT TRAN;
SELECT #rowcount AS AffectedRow;
END
END TRY
BEGIN CATCH
SELECT ##ERROR AS ERROR
ROLLBACK TRAN;
END CATCH
END
Or even better resign for using ##ERROR in TRY CATCH block:
ALTER PROCEDURE [dbo].[BUDGETUPDATE]
#DistrictID int
AS
BEGIN
SET NOCOUNT ON ;
BEGIN TRY
BEGIN TRAN
UPDATE bud
SET bud.BudgetStateID = #BudgetStateID
FROM [dbo].[BudgetOffice] bud
JOIN [dbo].[vw_Office] vw
ON vw.OfficeID = bud.OfficeID
WHERE vw.DistrictID = #DistrictID;
SELECT ##ROWCOUNT AS AffectedRow;
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE() AS ERROR
ROLLBACK TRAN;
END CATCH
END
And where is #BudgetStateID defined?

Syntax Trigger after update rollback

I'm searching for a correct syntaxt for a after update trigger with a rollback:
ALTER TRIGGER UpdatePlayer ON Players
AFTER UPDATE
AS
BEGIN
BEGIN TRY
IF EXISTS(select * from PlayerContract where id in (select id from deleted))
BEGIN
RAISERROR ('No update',16,1)
ROLLBACK
END
ELSE
BEGIN
PRINT 'UPDATE'
END
END TRY
BEGIN CATCH
ROLLBACK
DECLARE #ErrorMessage VARCHAR(500)
SELECT #ErrorMessage = ERROR_MESSAGE()
RAISERROR (#ErrorMessage, 16, 1)
END CATCH
END
When i try a insert, i get the follow error:
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.