Create query using BEGIN TRAN statement to insert two rows within BEGIN TRY if transaction count is greater than 1, catch error and rollback the transaction, otherwise print “transaction committed” (this is my teacher question the idea is that it should not insert more than 1 row by using the ##trancount is greater than one the transaction is rolled back)
This is the code that I wrote
BEGIN TRY
BEGIN TRAN;
INSERT INTO [Info].[Country]([name]) VALUES ('Italy');
PRINT ##TRANCOUNT
BEGIN TRAN
INSERT INTO [Info].[Country]([name]) VALUES ('Jorden');
PRINT ##TRANCOUNT
IF ##TRANCOUNT > 1
PRINT 'Rollback the transaction...';
ELSE
PRINT 'transaction succeeded';
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH;
It keeps inserting the two rows even though the trans count is greater than 1
How can I fix this?!!
This is the result
I suppose you're looking for using ##ROWCOUNT instead of ##TRANCOUNT
BEGIN TRY
BEGIN TRAN MyTran;
INSERT INTO Countries(Name) VALUES
('Italy'),
('Jorden');
IF ##ROWCOUNT > 1
ROLLBACK TRAN MyTran;
ELSE
COMMIT TRAN MyTran;
END TRY
BEGIN CATCH
RAISERROR ('Error Message',
16, -- Severity.
1 -- State.
);
ROLLBACK TRAN MyTran;
END CATCH;
If you really want to use nested transactions and use ##TRANCOUNT
BEGIN TRY
BEGIN TRAN MyTran;
INSERT INTO Countries(Name) VALUES ('Italy');
BEGIN TRAN MySecTran;
INSERT INTO Countries(Name) VALUES ('Jorden');
IF ##TRANCOUNT > 1
ROLLBACK TRAN;
ELSE
COMMIT TRAN;
END TRY
BEGIN CATCH
RAISERROR ('Error Message',
16, -- Severity.
1 -- State.
);
ROLLBACK TRAN;
END CATCH;
Here is a db<>fiddle where you can un-comment one of the two and see how it's working.
Update:
i checked it again. it did not insert any row in the table. but it should insert the first one
Then you need to save the tran as
BEGIN TRY
BEGIN TRAN MyTran;
INSERT INTO Countries(Name) VALUES ('Italy');
SAVE TRAN MySaveTran;
BEGIN TRAN MySecTran;
INSERT INTO Countries(Name) VALUES ('Jorden');
IF ##TRANCOUNT > 1
ROLLBACK TRAN MySaveTran;
COMMIT TRAN;
END TRY
BEGIN CATCH
RAISERROR ('Error Message',
16, -- Severity.
1 -- State.
);
ROLLBACK TRAN;
END CATCH;
BEGIN TRY
BEGIN TRAN;
INSERT INTO [Info].[Country]([name]) VALUES ('Italy');
print ##TRANCOUNT
Begin tran
INSERT INTO [Info].[Country]([name]) VALUES ('Jorden');
print ##TRANCOUNT
if ##TRANCOUNT>1
PRINT 'Rollback the transaction...';
else
print 'transaction successes';
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH;
GO
You are not actually doing a rollback, you're just printing 'Rollback the transaction' change to
BEGIN TRY
BEGIN TRAN;
INSERT INTO [Info].[Country]([name]) VALUES ('Italy');
print ##TRANCOUNT
Begin tran
INSERT INTO [Info].[Country]([name]) VALUES ('Jorden');
print ##TRANCOUNT
if ##TRANCOUNT>1
begin
PRINT 'Rollback the transaction...';
ROLLBACK TRANSACTION
end
else
print 'transaction successes';
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH;
GO
besides the other mentioned mistakes. I think your teacher wants you to make proper use of the try catch pattern. For this you need to know that the CATCH block is only executed when an error occurs inside of the try block.
See TRY...CATCH (Transact-SQL)
You can raise an error using the THROW statement
THROW (Transact-SQL)
IF ##ROWCOUNT > 1
THROW 51000, 'row count greater 1.', 1;
Related
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.
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
I'm running the following stored procedure and I'm receiving the error
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
what am I missing here?
CREATE PROCEDURE spImportData
AS
BEGIN TRANSACTION
BEGIN TRY
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType ON
END TRY
BEGIN CATCH
PRINT 'IDENTITY_INSERT IS ON'
END CATCH
GO
BEGIN TRY
INSERT INTO PINCDOCControlNew..tblActionType(ActionTypeID,ActionType,ActionTypeDescription)
SELECT ActionTypeID,ActionType,ActionTypeDescription
FROM PINCDOCControlOld..tblActionType
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType OFF
END TRY
BEGIN CATCH
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType OFF
EXECUTE usp_GetErrorInfo
END CATCH
BEGIN TRY
SET IDENTITY_INSERT PINCDOCControlNew..tblArea ON
END TRY
BEGIN CATCH
PRINT 'IDENTITY_INSERT IS ON'
END CATCH
GO
BEGIN TRY
INSERT INTO PINCDOCControlNew..tblArea(AreaID,AreaDescription,AreaNo)
SELECT AreaNo,AreaDescription,Area
FROM PINCDOCControlOld..tblArea
SET IDENTITY_INSERT PINCDOCControlNew..tblArea OFF
END TRY
BEGIN CATCH
SET IDENTITY_INSERT PINCDOCControlNew..tblArea OFF
EXECUTE usp_GetErrorInfo
END CATCH
IF ##ERROR <> 0
BEGIN
-- Rollback the transaction
ROLLBACK
-- Raise an error and return
RAISERROR ('Error in inserting.', 16, 1)
RETURN
END
COMMIT
I would try to have just a single BEGIN TRY .... END TRY block, in which you have all your logic that you want to execute. If anything goes wrong - anywhere in your logic - you'll be thrown into the BEGIN CATCH.... END CATCH block.
Start your transaction before your BEGIN TRY, and have the only COMMIT as the last statement in your TRY block - and in your CATCH block, have a rollback.
Something like this:
CREATE PROCEDURE dbo.spImportData
AS
BEGIN TRANSACTION
BEGIN TRY
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType ON
INSERT INTO
PINCDOCControlNew..tblActionType(ActionTypeID, ActionType, ActionTypeDescription)
SELECT
ActionTypeID, ActionType, ActionTypeDescription
FROM
PINCDOCControlOld..tblActionType
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType OFF
-- tblArea
SET IDENTITY_INSERT PINCDOCControlNew..tblArea ON
INSERT INTO
PINCDOCControlNew..tblArea(AreaID, AreaDescription, AreaNo)
SELECT
AreaNo, AreaDescription, Area
FROM
PINCDOCControlOld..tblArea
SET IDENTITY_INSERT PINCDOCControlNew..tblArea OFF
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
SET IDENTITY_INSERT PINCDOCControlNew..tblActionType OFF
SET IDENTITY_INSERT PINCDOCControlNew..tblArea OFF
EXECUTE usp_GetErrorInfo
RAISERROR ('Error in inserting.', 16, 1)
END CATCH
With this approach, you have exactly ONE BEGIN TRANSACTION, and either one single corresponding COMMIT TRANSACTION, or a single corresponding ROLLBACK TRANSACTION
I typically also add this SELECT statement to my CATCH block to get the error message and error code of what went wrong:
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
what am i missing here?
You probably have a surplus rather than a deficit. You need to remove the GO statements.
The only sensible interpretation of your script is that it is all intended to be part of the spImportData stored procedure definition.
This ends at the first GO statement (used by client tools to delimit batches) and the remaining batches are executed immediately in auto commit transactions as no explicit BEGIN TRAN has been run. When the COMMIT statement is reached there is nothing to commit hence the error.
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
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)