19

I am trying to put a try-catch statement inside a trigger using Microsoft Server 2005.

BEGIN TRANSACTION
BEGIN TRY
    --Some More SQL
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF (XACT_STATE()) = -1
    BEGIN
        ROLLBACK TRANSACTION;
    END;
END CATCH

The problem is that I don't want the trigger to fail if something is caught by the try-catch block. At the moment, I am getting the error "The transaction ended in the trigger. The batch has been aborted." if the transaction fails. How can I get the trigger to fail gracefully?


Additionally, if I remove the transaction, I get the error "Transaction doomed in trigger. Batch has been aborted.".

BEGIN TRY
    --Some More SQL
END TRY
BEGIN CATCH
    return
END CATCH

Is there any way around this?

gbn
  • 422,506
  • 82
  • 585
  • 676
Eldila
  • 15,426
  • 23
  • 58
  • 62
  • I am trying to update a legacy database whenever anything is inserted into a table. The problem is that I don't want the insert to fail if the trigger fails. The legacy database is not the most reliable system. – Eldila May 19 '09 at 22:12
  • 16
    I'm going to start using the word "doomed" in more of my error messages. – AaronLS Dec 22 '10 at 19:50

9 Answers9

9

Use SET XACT_ABORT OFF .When Transact-SQL statement encounter error ,it just raised the error message and the transaction continues processing. The following code is to create the trigger:

Create TRIGGER [dbo].tr_Ins_Table_Master ON [dbo].Table_Master
 AFTER INSERT
AS
BEGIN
set xact_abort off
BEGIN TRY
        --your SQL          
        INSERT INTO Table_Detail
        SELECT MasterID,Name FROM INSERTED

END TRY

BEGIN CATCH     
    select ERROR_MESSAGE()
END CATCH

END
神來之筆
  • 111
  • 1
  • 3
9

In my experience any error caught in a try catch in a trigger will rollback the entire transaction; you may be able to use a save transaction. I think you need to look at whats happening in "Some more sql" and determine if you can write case / if statements around it to stop the error.

What you may be able todo depending on what you are doing is use a save transaction and capture that in the catch

In your code something like this

SAVE TRANSACTION BeforeUpdate;
BEGIN TRY
        --Some More SQL
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION BeforeUpdate;
        return
END CATCH
u07ch
  • 13,324
  • 5
  • 42
  • 48
  • I didn't know about SAVE TRANSACTION +1 – ichiban May 19 '09 at 19:30
  • Its been there a while :) it does impact performance though so use sparingly. – u07ch May 19 '09 at 19:41
  • 1
    Does it mean that we must have an open transaction? Can we open it in beginning of the trigger? – meir Oct 02 '12 at 10:32
  • I too would like to know the answer to @meir question. I saw this "transaction is doomed" message in a trigger after what, as far as I can tell, was an operation that didn't even included a transaction. – Matt Dec 09 '13 at 17:08
  • 1
    @meir Triggers always have a transaction. If one does not already exist when a trigger starts, one will be created for it. – RBarryYoung Apr 25 '14 at 16:45
  • for anyone that comes here looking for an answer, here are a couple of references that elaborates the process of save transaction a little bit detailed. https://stackoverflow.com/a/7490088/6916098 | http://www.blackwasp.co.uk/SQLSavepoints.aspx – blogs4t Sep 03 '17 at 00:32
8

Don't rollback in a trigger and there is no need to start a transaction either.

The ROLLBACK TRANSACTION will rollback the original DML trigger and the extra trigger transaction too. So the batch will be aborted

Edit:

I suggest not having a "RETURN" in your catch block and simply allow the code to complete I've never ignored a trapped error in a trigger (but I do use TRY/CATCH in triggers with rollback and raiserror to re-throw) so this is a guess, but the return is probably an abnormal exit condition in the trigger

Also, try to avoid the error condition in the first place. Change the --some more sql to avoid the error. Example, add if exists(... to test for a duplicate first or similar

gbn
  • 422,506
  • 82
  • 585
  • 676
  • This is really helpful. However, I just ran into another problem. Please see the updated question for details. – Eldila May 19 '09 at 18:46
  • 1
    Unfortunately, without a `ROLLBACK` in a trigger -- which is very messy and can entirely muck up .NET transaction support! -- I do not know of a way to *absolutely enforce constraints* with triggers. `RAISERROR(...,16,1)` is enough to make .NET notice and throw an Exception in many cases, but "autocommit" transactions (IMPLICIT_TRANSACTIONS = OFF) are *not affected* by `RAISERROR` and will thus bypass any constraint this trigger was trying to add. The only "solution" I have found is to *not* use "autocommit" transactions and explicitly ROLLBACK/COMMIT with TRY/CATCH. Worst. Design. Ever. –  Mar 10 '12 at 02:31
  • (Of course Sybase has a perfectly viable `ROLLBACK TRIGGER` :-/) –  Mar 10 '12 at 02:34
3

This demo achieves many of the things asked above. Error messages become optional. The trick that makes it work is in a the nested dynamic execute.

    if object_id('toto')  is not  null drop table toto
    go
    create table toto (i int);
    go
    if object_id('toto2')  is not  null drop table toto2
    go
    create table toto2 (i int);
    go
    create Trigger trtoto
    ON toto
    Instead Of Insert
    as
    Begin
      BEGIN TRY
        set nocount  on
        insert into  toto  values(2)

        declare @sql nvarchar(max) =  'insert into toto2 values(3); select * from ThisTableDoesntexist'

        Exec sp_executeSql N'set xact_abort off; exec (@sql) ', N'@sql nvarchar(max)', @sql

      END TRY

      BEGIN CATCH
        PRINT  'Error on line ' + CAST(ERROR_LINE() AS VARCHAR(10))
        PRINT ERROR_MESSAGE()
      END CATCH
    End

GO
-- tests
set nocount on
insert into toto values (1)  -- is not inserted on purpose by  the trigger
select * from toto   -- other value inserted despite the error
select * from toto2  -- other value inserted in other table despite the error
  • It's almost 2021....wrote a stored proc to execute an http request against google maps...wanted to use it behind a trigger to do geocoding...this was the only way to not bother the transaction that was encapsulating the trigger...well done Mr Pelchat....now I don't have to bother thinking about async triggers and stuff like that... – nenea Dec 04 '20 at 22:02
2

To avoid losing the transactional data prior to the trigger action, you'll want to call COMMIT TRAN. Do this before the TRY/CATCH block and you will get your desired results.

Example:

COMMIT TRAN
BEGIN TRY
    -- possible error occurs here...
END TRY
BEGIN CATCH
    PRINT 'Error on line ' + CAST(ERROR_LINE() AS VARCHAR(10))
    PRINT ERROR_MESSAGE()
END CATCH

It will throw the following error still - not sure how to avoid:

The transaction ended in the trigger. The batch has been aborted.

But both the original transaction and the trigger transaction should commit successfully.

UPDATE: To avoid the exception last error, call BEGIN TRAN within the TRY statement. Note, Microsoft recommends to NOT call COMMIT TRAN within a trigger, but if unavoidable, this should work for you.

Example:

COMMIT TRAN
BEGIN TRY
    BEGIN TRAN
rjchicago
  • 586
  • 1
  • 7
  • 7
1

Isn't the best way but it works. Start a new transaction and do the normal commit rollback and begin another transaction in the end for implicit transaction commit

http://msdn.microsoft.com/en-us/library/ms187844(v=SQL.90).aspx

Khawaja
  • 11
  • 1
1

u07ch,

Unfortunately you cannot use save transaction and try.. catch together - they simply cannot work together:

http://sqlblog.com/blogs/alexander_kuznetsov/archive/2008/11/15/avoid-mixing-old-and-new-styles-of-error-handling.aspx

A-K
  • 16,804
  • 8
  • 54
  • 74
  • 1
    It can be done; [here is an example from Microsoft](http://msdn.microsoft.com/en-us/library/ms188378.aspx). It's just a PITA. You need to know if you created the transaction (and how) and then you need to look at XACT_STATE on cleanup... and that isn't even in *context of a trigger* which is different bit of ugly entirely. I hate SQL Server transactions and "exception handling". –  Mar 10 '12 at 02:37
  • @pst sometimes it cannot be done: if your transaction is doomed, there is no way you can roll back to your savepoint. All you can do is rollback te whole transaction. – A-K Mar 11 '12 at 03:30
  • Naturally ... the example also covers that case. –  Mar 11 '12 at 05:28
1

It might be helpful to know what your are trying to do in the trigger.

The trigger is part of the transaction that sent the data to the inserted or deleted tables. If it fails, it will rollback the whole transaction. If you are expecting the trigger to fail occasionally but not rollback the statement that casued the trigger to fire, then perhaps you need to rethink whether a trigger is the right thing to use.

HLGEM
  • 94,695
  • 15
  • 113
  • 186
0

You can set XACT_Abort to OFF in the beginning of the trigger.

Jami
  • 579
  • 6
  • 20