Try catch in trigger not suppressing error - sql

I have a trigger and I want to surround the dml statements in the trigger by a try catch block so that any exception that occur in the trigger does not throw any exceptions outside the trigger. But the error is not suppressed.
My trigger is :
ALTER TRIGGER [dbo].[Deal.OnInsertUpdateAddDealAuditDetails]
ON [dbo].[Deal]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
BEGIN TRY
--SOME DML STATEMENTS
select 1/0
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
The error output is:
Msg 3616, Level 16, State 1, Line 1
An error was raised during trigger execution. The batch has been aborted and the user transaction, if any, has been rolled back.

XACT_ABORT is implicitly ON inside triggers. It rolls back the current transaction when a Transact-SQL statement raises a run-time error.
You have to handle the exception in original Insert query to aboid throwing the error.
BEGIN TRY
INSERT INTO deal(col1,col2,..)
VALUES (val1,val2,..)
END TRY
BEGIN CATCH
SELECT Error_number() AS ErrorNumber,
Error_message() AS ErrorMessage;
END CATCH
Note : The inserted records will not be present in the table. If you want the inserted records to be present in table though the trigger failed then you may have to commit the transaction first inside the trigger

Related

Intermediate commit along with Rollback

I have save point issued to commit 300 records in C . However once a fatal comes(divide by 0) i would want to rollback the records processsed , as well as update one table for recording this fatal record. How can i do commit for this fatal table alone and rollback previous records.
If you use SQL server :
Use TRY - CATCH method : If fatal error occurred then it goes to catch block and rollback transactions and after that rollback update fatal record into one table.If fatal error not occurred transactions committed as usual.
CREATE PROCEDURE Procedure_Name
(
#Parameter1 Data_type,
#Parameter2 Data_type
)
AS
BEGIN TRY
--- your SQL statements
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
--After rollback fatal record SQL statements
INSERT (or) UPDATE your fatal record
END CATCH

SQL Server - semicolon before THROW

As we know ending each statement with semicolon is a good practice. Let's assume we have some old code which uses RAISERROR to rethrow exceptions and we want to exchange it with THROW.
From: THROW:
The statement before the THROW statement must be followed by the
semicolon (;) statement terminator.
Using only ROLLBACK:
BEGIN TRY
-- some code
SELECT 1;
END TRY
BEGIN CATCH
-- some code
ROLLBACK
THROW;
END CATCH
and we get Incorrect syntax near 'THROW'. which is perfectly valid.
But using ROLLBACK TRANSACTION works without semicolon:
BEGIN TRY
-- some code
SELECT 1;
END TRY
BEGIN CATCH
-- some code
ROLLBACK TRANSACTION
THROW;
END CATCH
LiveDemo
Finally using ROLLBACK TRANSACTION #variable:
DECLARE #TransactionName NVARCHAR(32) = 'MyTransactionName';
BEGIN TRY
-- some code
SELECT 1;
END TRY
BEGIN CATCH
-- some code
ROLLBACK TRANSACTION #TransactionName
THROW;
END CATCH
and once again we get Incorrect syntax near 'THROW'..
Is there any particular reason why the second example works(backward compatiblity/...)?
EDIT:
There is great article written by Erland Sommarskog: Using ;THROW
If you alter the code so it actually throws an error.
BEGIN TRY
-- some code
SELECT 1/0;
END TRY
BEGIN CATCH
-- some code
ROLLBACK TRANSACTION
THROW;
END CATCH
You see
Msg 3903, Level 16, State 1, Line 7 The ROLLBACK TRANSACTION request
has no corresponding BEGIN TRANSACTION.
It is trying to roll back to a save point called THROW.
This is valid syntax but fails at runtime in the above example as no transaction exists. If you are in an open transaction but have no such save point (as below)
BEGIN TRY
BEGIN TRAN
SELECT 1/0;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
THROW;
END CATCH
ROLLBACK
you see the following instead.
Cannot roll back THROW. No transaction or savepoint of that name was
found.
This kind of ambiguity is presumably why the requirement for a preceding semi colon before throw exists.

COMMIT SQL TRANSACTION IN ROLLBACK

I have created stored procedure with TRY CATCH. In CATCH section i am executing another stored procedure to store error in table Error_Details.
Now in C#, I am using DbTransaction to commit and rollback depend upon error.
Problem is, while rollback changes table Error_Details also getting rolled back.
Is there any option where everything should rolled back but not Error_Details table?
Use the transactions inside the stored procedure itself.
Please see one sample case below.
BEGIN TRY
BEGIN TRANSACTION MyActivity
-- Your SQL COMMANDS
COMMIT TRANSACTION MyActivity
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION MyActivity
END
INSERT INTO ERROR_DETAILS --INSERTING ErrorInfo INTO LOG TABLE
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
END CATCH

Use THROW througout usp for data validation, with rollback?

I have a query that checks data up front coming from the application to make sure the application is not passing bad data. If there is a piece of bad data a THROW is used to raise an error. Is this keeping my transactions open because I am not hitting the catch block then? If so, how would I handle this?
For Example:
BEGIN TRY
BEGIN TRANSACTION
IF NOT EXISTS(SELECT 1 FROM dbo.lutCode WHERE ID = #CodeID)
BEGIN
THROW('50001','Test Error',1)
END
UPDATE emp
SET emp.CodeID = #CodeID
WHERE emp.ID = #EmployeeID
IF XACT_STATE() = 1 COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() = -1 ROLLBACK TRANSACTION
THROW;
END CATCH
The query is a lot more complex than this, and in the real query the IF EXISTS does need to be in the transaction, so putting it outside of the transaction is not an option.
I would handle this a little differently ,
Do not open a transaction until you have done your validations, If validation fails raise an error in the try block and control will jump to catch block ignoring/skipping rest of the code in try block.
I have added the check IF(##TRANCOUNT <> 0) ROLLBACK TRAN because error maybe raised during validation and in that case a transaction will never be opened.
Only if something goes wrong while the update statement is being executed the control will jump to catch block without committing the transaction and there it will be rolled back and rest of the error logging stuff will be executed.
BEGIN TRY
-- do validatiion before openning transaction
IF NOT EXISTS(SELECT 1 FROM dbo.lutCode WHERE ID = #CodeID)
BEGIN
RAISERROR('Test Error',16, 1)
END
-- if test passed now open transaction
BEGIN TRANSACTION;
UPDATE emp
SET emp.CodeID = #CodeID
WHERE emp.ID = #EmployeeID
-- commit transaction if nothing gone wrong
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Rollback transaction if something went wrong
-- after you opened the trasaction
IF (##TRANCOUNT <> 0)
BEGIN
ROLLBACK TRANSACTION;
END
-- Other error logging
SELECT ERROR_LINE() AS Errorline
,ERROR_MESSAGE() AS ErrorMessage
,ERROR_NUMBER() AS ErrorNumber .......
END CATCH
change the statement in the catch block to this:
if xact_state() = 1 rollback tran
-1 only occurs in very specific circumstances, which I dont think you're getting in your query. If you change it to rollback on any xact_state which is not 0, you should stop getting hung transactions.
Also, I don't think you are supposed to use THROW in the TRY block; just the catch block. You'd want to use raiserror() to trigger an error in the try block, and then if you choose, use throw in the catch block.

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.