Tell SQL Server the error is "handled" in try...catch - sql-server-2005

I'd like to indicate to SQL Server 2005, in my BEGIN CATCH...END CATCH block that the error is "handled"... That is, clear the error.
Is that possible? Consider this:
begin transaction
begin try
begin transaction
select cast('X' as bit)
commit transaction
end try
begin catch rollback transaction
select error_number(), error_message()
end catch
commit transaction
This results in the following:
(0 row(s) affected)
(No column name) (No column name)
245 Conversion failed when converting the varchar value 'X' to data type bit.
(1 row(s) affected)
Msg 3902, Level 16, State 1, Line 13
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
Thanks.
A.

Not all errors are maskable. You are always supposed to inspect the XACT_STATE() and see if you can continue. Certain errors (1205 deadlock being a typical example) will rollback the transaction and not allow you to continue.
What you describe (a loop which can preserve the work) is ussualy done with the help of a savepoint:
begin transaction
begin try
while #loopcondition
begin
save transaction loop;
begin try
-- process loop element here
end try
begin catch
if xact_state() = -1
begin
-- whole transaction is doomed
rollback;
raiserror ('Aborting', ....);
end
else if xact_state() = 0
begin
-- trasaction was aborted by inner loop
raiserror ('Aborted inside', ....);
end
else if xact_state() = 1
begin
-- this error is recoverable, rollback to the savepoint and continue the loop
rollback loop
end
end catch
-- continue loop here
fetch next from ....
/*
-- batch commit here if batch committing
if #batchsize
begin
commit;
begin transaction
end
*/
end
commit;
end try
begin catch
-- if we get here, we could not handle the error inside the loop and continue
if xact_state() != 0
rollback
raiserror('failed to process', ...)
end catch

Related

Write stored procedure so if one statement fails it should not effect the other?

I have this procedure which basically insert data.
Begin Transaction
Insert into [dbo].Values
(
EQ
)
values
(
#EQ
)
End
--Set #STATUSRet= 'Created'
--Set #ErrorRet= ''
Commit Transaction
End Try
Begin Catch
Set #STATUSRet= 'Failed'
Set #ErrorRet= (Select ERROR_MESSAGE())
Rollback Transaction
End Catch
Now I want to add a piece of code that calls another database server and insert data into the table in that server i.e. remotely. That's ok I will do that but if that fails then that should not effect my current process of inserting the data as I have described above i.e. if the remote data insertion fails it should not effect the prior insert in any way and should return successfully to the calling application behaving like nothing happened.
The default method of controlling transactions is auto-commit:
Any single statement that changes data and executes by itself is
automatically an atomic transaction. Whether the change affects one
row or thousands of rows, it must complete successfully for each row
to be committed. You cannot manually rollback an auto-commit
transaction.
So, if the two inserts are not wrapped in explicit transaction this will be the behavior. If you have more code blocks, then you can use two separate explicit transactions blocks like this:
DECLARE #ExecuteSecondTransaction BIT = 0;
-- local database
BEGIN TRY
BEGIN TRANSACTION;
-- CODE BLOCK GOES HERE
SET #ExecuteSecondTransaction = 1;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
-- GET ERRORS DETAILS OR THROW ERROR
END CATCH;
-- remote database
IF #ExecuteSecondTransaction = 1
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
-- CODE BLOCK GOES HERE
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION
END;
-- GET ERRORS DETAILS OR THROW ERROR
END CATCH;
END;

Rollback done - yet this error : Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements

I am getting this error in stored procedure.
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
I read through some of the answers and found that if I return without a commit, I get the error. My stored procedure is something like this:
BEGIN TRY
BEGIN
if #id is null
BEGIN
set #id= (SELECT last_sequence_value FROM table_name WHERE sequence_name = 'id') + 1
BEGIN
BEGIN TRANSACTION
-- update SQL statement here
IF ##ROWCOUNT = 0
BEGIN
ROLLBACK TRANSACTION
RAISERROR('There was an error getting unique id in the table.',10,1)
RETURN
END
IF (##ERROR <> 0)
BEGIN
ROLLBACK TRANSACTION
RAISERROR('There was an error updating record to the table',10,1)
RETURN
END
COMMIT TRANSACTION
END
END
else
BEGIN
-- some sql select statements
END
END
END TRY
BEGIN CATCH
-- Raise an error with the details of the exception
RAISERROR(#ErrMsg, #ErrSeverity, 1) WITH SETERROR
END CATCH
From the above code, I am doing a rollback and returning from the stored procedure. Yet when I run this in the perf testing environment, I get the error mentioned earlier.
Kindly help regarding this.
I have fixed it by adding
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
in the catch block before I raise an error.
Thanks everyone for the wonderful inputs.
I had a user report this same error in code I inherited, but it was caused by a combination of using a transaction and try/catch inside of the sproc along with calling the sproc inside of a VB.Net System.Transactions.TransactionScope block as well. If an error cropped up inside the sproc, the sproc rolled back everything there correctly, but it apparently rolled back the VB.Net transaction as well and created the mismatch. My fix was to remove the transaction code and the the try/catch blocks from the sproc since the VB.Net transaction will rollback all the inserts/updates inside the sproc if the sproc failed or if the other processing done inside the VB.Net transaction block failed as well. Hopefully this will save someone else a day of frustration.
Here you should use try catch blog inside begin transaction. like this
BEGIN
if #id is null
BEGIN
set #id= (SELECT last_sequence_value FROM table_name WHERE sequence_name = 'id') + 1
BEGIN
BEGIN TRANSACTION
BEGIN TRY
-- update SQL statement here
IF ##ROWCOUNT = 0
BEGIN
ROLLBACK TRANSACTION
RAISERROR('There was an error getting unique id in the table.',10,1)
RETURN
END
IF (##ERROR <> 0)
BEGIN
ROLLBACK TRANSACTION
RAISERROR('There was an error updating record to the table',10,1)
RETURN
END
END TRY
BEGIN CATCH
-- Raise an error with the details of the exception
RAISERROR(#ErrMsg, #ErrSeverity, 1) WITH SETERROR
END CATCH
COMMIT TRANSACTION
END
END
else
BEGIN
-- some sql select statements
END
END
First, compare
DECLARE #ErrorVar INT
RAISERROR(N'Message', 16, 1);
IF ##ROWCOUNT <> 0
PRINT 'Rows <> 0'
IF ##ERROR <> 0
PRINT N'Error = ' + CAST(##ERROR AS NVARCHAR(8));
GO
And this
DECLARE #ErrorVar INT,
#Error INT,
#Cnt INT
RAISERROR(N'Message', 16, 1);
SELECT #ERROR = ##ERROR, #Cnt = ##ROWCOUNT
IF #Cnt <> 0
PRINT 'Rows 0'
IF #ERROR <> 0
PRINT N'Error = ' + CAST(##ERROR AS NVARCHAR(8));
GO
https://learn.microsoft.com/en-us/sql/t-sql/functions/error-transact-sql
Because ##ERROR is cleared and reset on each statement executed, check it immediately following the statement being verified, or save it to a local variable that can be checked later. +

Why are these nested SQL Server transactions throwing a mismatch error if there is a rollback?

By running 'Test Errors' I get unexpected results. I thought by checking for ##Trancount it would avoid mismatches. Can anyone help me with a better way to rollback errors? I want to rollback all transactions which are nested. Stored procedures can be both nested and on their own.
alter procedure TestErrors
as
begin
begin try
begin transaction
exec TestErrorsInner;
IF ##TRANCOUNT > 0
commit transaction;
end try
begin catch
IF ##TRANCOUNT > 0
rollback transaction;
select ERROR_MESSAGE();
end catch
end
alter procedure TestErrorsInner
as
begin
begin try
begin transaction
RAISERROR('Test Error',16,1);
IF ##TRANCOUNT > 0
commit transaction;
end try
begin catch
IF ##TRANCOUNT > 0
rollback transaction;
select ERROR_MESSAGE();
end catch
end
Results:
Test Error
Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. Previous count = 1, current count = 0.
This is because you are catching a transaction in the TestErrors which is not in Active state.
You have already rolled back your transaction in Catch block of TestErrorsInner.
Then again you are trying to do COMMIT/ROLLBACK it in TestErrors. So it is throwing an error.
It is your responsibility to Raise an Error explicitly again in Catch block of TestErrorsInner. So that Error will be the input for Parent SP.
So your TestErrorsInner should be like
ALTER PROCEDURE TESTERRORSINNER
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
RAISERROR('TEST ERROR',16,1);
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
--SELECT ERROR_MESSAGE();
RAISERROR('TEST ERROR in Catch',16,1); --Here Raised
END CATCH
END
Now execute the TestErrors Stored procedure, you won't get that error.
And You can check the Transaction Status with XACT_STATE()
Calling XACT_STATE() will give result of 0 or 1 or -1 (From MSDN)
If 1, the transaction is committable.
If -1, the transaction is uncommittable and should be rolled back.
if XACT_STATE = 0 means there is no transaction and a commit or rollback operation would generate an error.

There are uncommitted transactions

SET XACT_ABORT ON
BEGIN TRY
BEGIN TRAN
INSERT INTO dbo.Student
(FirstName, LastName)
VALUES
('Jon','Ye')
IF XACT_STATE() = 0
COMMIT TRAN
END TRY
BEGIN CATCH
IF XACT_STATE() <> 1
ROLLBACK TRAN
ELSE
COMMIT TRANSACTION
END CATCH
RETURN
GO
Error Message:
There are uncommitted transactionS.
I only see results when I close SQL Server.
the problem is here
IF XACT_STATE() = 0
COMMIT TRAN
XACT_STATE() only returns 0 if no active user transaction exists. Due to the open transaction started by BEGIN TRAN, XACT_STATE must be returning 1 and the COMMIT TRAN consequently does not execute. You should be checking for a return value of 1

a PRINT 'Success' that screw up a begin trans/ begin try commit / end try?

So I just saw a weird behavior
In one script there is something like:
begin transaction
begin try
stuff
stuff
stuff
print 'commit'
commit transaction
end try
begin catch
print 'rollback'
print error_message()
rollback transaction
end catch
thing is when this script in run, I see the print commit message but it does not make the commit and lock the tables/rows/etc
I have to manually run a commit by selecting the line and run it.
but if I do this
begin transaction
begin try
stuff
stuff
stuff
commit transaction
print 'commit'
end try
begin catch
print error_message()
rollback transaction
print 'rollback'
end catch
(swapped the print and the commit)
it work fine.
anyone know why this would happen?
this works fine for me:
--create table t (rowid int) --create one time before running script
begin transaction
begin try
insert into t values (1)
print 'commit'
print XACT_STATE() --should be 1
commit transaction
print XACT_STATE() --should be 0
end try
begin catch
print ERROR_MESSAGE()
rollback transaction
print 'rollback'
end catch
select * from t
output
commit
1
0
rowid
-----------
1
Close your SSMS window, open a new window, and then run your 1st script again, I'll bet you had an open transaction the first time you ran it, so you needed that extra COMMIT.
EDIT after OP comment:
run this exact script in a new connection to each database:
BEGIN TRY create table t (rowid int) END TRY BEGIN CATCH END CATCH
print 'A - XACT_STATE()='+ISNULL(CONVERT(varchar(10),XACT_STATE()),'')+', ##TRANCOUNT='+ISNULL(CONVERT(varchar(10),##TRANCOUNT),'')
begin transaction
begin try
insert into t values (1)
print 'commit'
print 'B - XACT_STATE()='+ISNULL(CONVERT(varchar(10),XACT_STATE()),'')+', ##TRANCOUNT='+ISNULL(CONVERT(varchar(10),##TRANCOUNT),'')
commit transaction
print 'C - XACT_STATE()='+ISNULL(CONVERT(varchar(10),XACT_STATE()),'')+', ##TRANCOUNT='+ISNULL(CONVERT(varchar(10),##TRANCOUNT),'')
end try
begin catch
print ERROR_MESSAGE()
rollback transaction
print 'rollback'
end catch
print 'D - XACT_STATE()='+ISNULL(CONVERT(varchar(10),XACT_STATE()),'')+', ##TRANCOUNT='+ISNULL(CONVERT(varchar(10),##TRANCOUNT),'')
select * from t
you should get this:
A - XACT_STATE()=0, ##TRANCOUNT=0
(1 row(s) affected)
commit
B - XACT_STATE()=1, ##TRANCOUNT=1
C - XACT_STATE()=0, ##TRANCOUNT=0
D - XACT_STATE()=0, ##TRANCOUNT=0
rowid
-----------
1
(1 row(s) affected)