Cannot raise/throw exception inside CATCH block SQL - sql

I need to throw/raise an exception in SQL to catch in C# application. However, whenever I throw an error inside CATCH block the error is not actually thrown.
Here is the SQL code I am using:
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO Transactions (Id, Amount)
VALUES (NEWID(), 43346);
INSERT INTO Transactions (Id, Amount)
VALUES (NEWID(), 'fas');
COMMIT TRANSACTION
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE #ErrorSeverity INT = ERROR_SEVERITY();
DECLARE #ErrorState INT = ERROR_STATE();
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState);
THROW 50001, #ErrorMessage, #ErrorSeverity;
END CATCH;
I tried both THROW and RAISERROR methods. However, none of them works. I tested it in my app and in DBeaver but I still cannot raise an exception. Moreover, when I raise an exception outside of CATCH block, then the exception is raised using both THROW and RAISERRORmethods. Not sure what I am doing wrong.

Related

How to stop insert in a transaction in SQL Server?

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;

How to throw inside a stored procedure that uses a save point?

I am trying to THROW an error from a stored procedure, that uses a save point to fall back to if nested, but it is never thrown.
I searched around a bit and all I find are solutions (did not try them) that use RAISERROR, however the Microsoft documentation say's RAISERROR should not be used any longer, instead THROW should be used.
Here is some small example.
CREATE OR ALTER PROC sp_should_throw_error
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT OFF
DECLARE #TranCounter INT;
SET #TranCounter = ##TRANCOUNT;
IF #TranCounter > 0
SAVE TRANSACTION ProcedureSave;
ELSE
BEGIN TRANSACTION;
BEGIN TRY
THROW 50001, 'Spitted out an error.', 1
IF #TranCounter = 0
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF #TranCounter = 0 ROLLBACK TRANSACTION;
ELSE IF XACT_STATE() <> -1 ROLLBACK TRANSACTION ProcedureSave;
END CATCH
END
EXEC sp_should_throw_error
I hope anyone can point out to me what I am doing wrong here. Why is the error never thrown?
You need a THROW in the final CATCH block to re-raise the error. Otherwise, it won't be raised to the client because you've caught and handled it.
CREATE OR ALTER PROC usp_should_throw_error
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT OFF
DECLARE #TranCounter INT;
SET #TranCounter = ##TRANCOUNT;
IF #TranCounter > 0
SAVE TRANSACTION ProcedureSave;
ELSE
BEGIN TRANSACTION;
BEGIN TRY
THROW 50001, 'Spitted out an error.', 1
IF #TranCounter = 0
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF #TranCounter = 0 ROLLBACK TRANSACTION;
ELSE IF XACT_STATE() <> -1 ROLLBACK TRANSACTION ProcedureSave;
THROW;
END CATCH
END;
GO

Anticipating fatal errors when using stored procedures

Please see the code below:
ALTER PROCEDURE GetPerson
AS
BEGIN TRANSACTION;
BEGIN TRY
DECLARE #TestVariable1 AS INT;
DECLARE #TestVariable2 AS INT;
SET #TestVariable1 = 1;
SET #TestVariable2 = 0;
DECLARE #TestVariable3 AS INT;
SET #TestVariable3 = #TestVariable1 / #TestVariable2;
PRINT 'transaction committed';
END TRY
BEGIN CATCH
ROLLBACK;
PRINT 'transaction rolled back';
END CATCH
I could run this using the command:
EXEC GetPerson
The transaction is successfully rolled back because you cannot divide my zero.
Now see the code below:
ALTER PROCEDURE GetPerson
AS
BEGIN TRANSACTION;
BEGIN TRY
CREATE TABLE #Test
(
test INT
);
SELECT TOP 1 *
FROM #Test;
SELECT TOP 1 *
FROM person.person;
DROP TABLE #Test;
SELECT TOP 1 *
FROM #Test;
PRINT 'transaction committed';
END TRY
BEGIN CATCH
ROLLBACK;
PRINT 'transaction rolled back';
END CATCH
If I run the same comment:
EXEC GetPerson
then I get an error:
Msg 208, Level 16, State 0, Procedure GetPerson, Line 11
Invalid object name '#Test'.
Msg 266, Level 16, State 2, Procedure GetPerson, Line 11
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1
I understand that not all errors are caught by the TRY clause as explained here: https://msdn.microsoft.com/en-gb/library/ms178592.aspx
When RAISERROR is run with a severity of 11 or higher in a TRY block, it transfers control to the associated CATCH block
My question is: how do you deal with these? I know in my code it is a clear developer mistake to try to select from a temp table that no longer exists. However, I am wandering if there are other errors I should deal with.
Is it acceptable practice to do this when calling the stored procedure:
EXECUTE GetPerson ;
IF ##trancount >= 1
ROLLBACK;
Alternatively I could do something like this:
EXEC GetPerson
IF ##trancount >= 1
INSERT INTO dbLog ("Trancount greater than 1 after running GetPerson")
Error 208 is a compilation error. Your code never run, anything, because it could not even compile. This is the same as asking a C# program to catch a compile error.
Besides, if you want a correct try/catch block that handles exceptions and transaction, see 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
Obviously this will not handle syntax/binding errors (like 208), but at least will handle correct procedures. Remember that checking XACT_STATE() in the CATCH block is mandatory. Consider an error like 1205 (deadlock) where the CATCH block runs after the transaction was rolled back. You also need to honor and handle caller's transaction.

SQL SERVER 2008R2 Nested Transactions with RAISERROR

We are going through a process of switching from DB2 to SQL Server 2008R2 and I'm a bit unfamiliar with TSQL. Any help getting a better understanding of what is occurring would be nice. We've created a procedure called RethrowError as:
CREATE PROCEDURE RethrowError
AS
BEGIN
-- Return if there is no error information to retrieve.
IF ERROR_NUMBER() IS NULL
RETURN;
PRINT 'yo error';
DECLARE
#ErrorMessage NVARCHAR(4000),
#ErrorNumber INT,
#ErrorSeverity INT,
#ErrorState INT,
#ErrorLine INT,
#ErrorProcedure NVARCHAR(200);
-- Assign variables to error-handling functions that
-- capture information for RAISERROR.
SELECT
#ErrorNumber = ERROR_NUMBER(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorLine = ERROR_LINE(),
#ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
-- Build the message string that will contain original
-- error information.
SELECT #ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' +
'Message: '+ ERROR_MESSAGE();
PRINT 'yo doin something';
-- Raise an error: msg_str parameter of RAISERROR will contain
-- the original error information.
RAISERROR
(
#ErrorMessage,
#ErrorSeverity,
1,
#ErrorNumber, -- parameter: original error number.
#ErrorSeverity, -- parameter: original error severity.
#ErrorState, -- parameter: original error state.
#ErrorProcedure, -- parameter: original error procedure name.
#ErrorLine -- parameter: original error line number.
);
PRINT 'yo end';
RETURN;
END
GO
The reason we created the procedure is purely to expand on errors in the future without having to touch all the procedures. I've added some PRINT lines for debugging purposes.
My main question is that we have procedure A and on failure it executes RethrowError and I'll see the messages
yo error
yo doin something
yo end
as expected.
CREATE PROCEDURE dbo.A
AS
BEGIN
SET NOCOUNT ON;
DECLARE & SET VARIABLES;
BEGIN TRY
BEGIN TRANSACTION MaintainTarget
DO SOME STUFF
END TRY
BEGIN CATCH
EXEC RethrowError;
IF (XACT_STATE()) = -1
BEGIN
PRINT
N'The transaction is in an uncommittable state. ' +
'Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
IF (XACT_STATE()) = 1
BEGIN
PRINT
N'The transaction is committable. ' +
'Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
RETURN -101;
END CATCH;
RETURN;
END
GO
However, we've created a procedure that executes many procedures and when a nested procedure (ie procedure A being called by procedure B) fails the only messages I see are
yo error
yo doin something
I'm don't quite understand why the last message no longer shows up.
The procedure B is similar to procedure A but with a little difference in the catch.
CREATE PROCEDURE dbo.B
AS
BEGIN
SET NOCOUNT ON;
DECLARE & SET VARIABLES;
BEGIN TRY
DO SOME STUFF
END TRY
BEGIN CATCH
COMMIT;
RETURN -101;
END CATCH;
RETURN;
END
Any help on getting a better understanding what is happening would be appreciated.
I allowed myself to edit your code to mimic the bahaviour, but keep it simple (your job, actually ;).
Your procA works fine, because RethrowError procedure is being called inside CATCH block of procA and everything executes. But in your second case, it all still happens inside TRY block of procB! So CATCH part of procB fires immediately after RAISERROR in RethrowError is called.
This simple example demonstrates this behaviour of TRY-CATCH:
begin try
select 1/0
print 'doesnt show - div error'
end try
begin catch
print 'oops'
select 1/0
print 'this one shows because its in CATCH!'
end catch
And here's your simplified code:
-- "proc B" start
begin try
-- "proc A" start (works fine alone)
begin try
begin tran
select 1/0 --error
end try
begin catch
print 'yo error';
RAISERROR ('RE from RethrowError', 16, 1) --comment this out and see what happens
print 'yo end';
IF (XACT_STATE())=-1 or (XACT_STATE())=1
BEGIN
PRINT N'Rolling back transaction.'
ROLLBACK TRANSACTION;
end
end catch -- "proc A" ends
end try
begin catch
select error_message(), error_severity(), error_state() --
print 'outer catch';
commit;
end catch;
Hope this helps.

SQL Server transaction handling

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.