I'm trying to trigger an update on a specific user in my user table.
Basically is someone tries to change that user then I want to rollback whatever query was executed and disable the user(using a SP), but if if is not that user, than proceed with the update commit.
This is the code I created:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[Users_Trigger_UPDATE]
ON [dbo].[Users]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @user_id as nvarchar(30);
DECLARE @action as nvarchar(30) = 'UPDATE';
DECLARE @source as nvarchar(30) = (SELECT client_net_address FROM
sys.dm_exec_connections WHERE session_id = @@SPID);
SELECT
@user_id = inserted.UserID
FROM
inserted
IF @user_id = 'Admin'
BEGIN
ROLLBACK TRAN;
EXEC [dbo].[DisableUser] @user_id, @action, @source;
PRINT 'Admin User Triggered';
END
ELSE
BEGIN
PRINT 'Other User';
COMMIT;
END
END
But I'm receiving the error "The transaction ended in the trigger. The batch has been aborted."
I tried to use the TRY...CATCH
but the error is the same. I didn't create a nested transaction so I'm assuming that when I use the ROLLBACK
or COMMIT
that will apply to the original Query that triggered the UPDATE
.
I tried an INSTEAD OF
trigger, but the result is the same.
The result of the trigger is correct, it's doing what I want on the DB side, the problem is that there is software using this table that probably has a Try...Catch
and as the trigger returns an error, even if the result is correct, the software gives a Login error because the trigger at the end has an error.
Best Answer
While I am not advocating the approach being attempted here (i.e. manipulating the Transaction within the Trigger), I at least wanted to explain what was going on so that there was a clearer understanding of how Triggers work.
Here are some basic behaviors of Triggers:
@@TRANCOUNT
== 0 before the DML statement, then@@TRANCOUNT
will be 1 in the Trigger@@TRANCOUNT
> 0 before the DML statement, then@@TRANCOUNT
will be the same value in the Trigger@@TRANCOUNT
needs to be the same before and after execution, Triggers allow for@@TRANCOUNT
being higher or lower after their execution, with one exception (see next item).@@TRANCOUNT
being 0 (before the Trigger exits), thenCOMMIT
orROLLBACK
, theinserted
anddeleted
tables no longer have any rows in them. If you need anything from them after the Transaction ends, then you need to capture it into variables and/or a temp table (or table variable) before the Transaction ends.XACT_ABORT ON
. This might complicate things, even if you have aTRY...CATCH
construct. In some cases you might need to explicitly setXACT_ABORT OFF
That said, you certainly can
COMMIT
/ROLLBACK
the Transaction within a Trigger. Simplistically, if you do either of those actions, you just need to issue aBEGIN TRAN;
before the Trigger ends so that you don't get the error about it aborting the batch. This assumes, in the case ofROLLBACK
, that you want to mask the fact that this happened instead of allowingROLLBACK
to cancel the entire operation, which is how it is normally used within Triggers.However, reality is not so simple:
To account for the possibility of an active Transaction already existing prior to the Trigger firing, you would need to capture
@@TRANCOUNT
at the top and then issue the correct number ofBEGIN TRAN;
, and possibly alsoCOMMIT TRAN;
, statements:Again, this is not recommended as it changes the nature of any existing Transaction to behave in an unexpected, non-intuitive way. The flow would end up being:
COMMIT
orROLLBACK
The definitive reference on all this is Error and Transaction Handling in SQL Server by Erland Sommarskog.