ユーザーテーブルの特定のユーザーに対して更新をトリガーしようとしています。基本的に誰かがそのユーザーを変更しようとする場合、実行されたクエリをロールバックしてユーザーを無効にします(SPを使用)。ただし、そのユーザーでない場合は、更新コミットを続行します。
これは私が作成したコードです:
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
しかし、「トランザクションがトリガーで終了しました。バッチは中止されました。」というエラーが表示されます。
TRY...CATCH
を使用しようとしましたが、エラーは同じです。ネストされたトランザクションを作成しなかったので、ROLLBACK
またはCOMMIT
を使用すると、UPDATE
をトリガーした元のクエリに適用されると想定しています。
INSTEAD OF
トリガーを試しましたが、結果は同じです。
トリガーの結果は正しいです。DB側で必要なことを行っています。問題は、このテーブルを使用しているソフトウェアにTry...Catch
があり、トリガーがエラーを返すため、結果が正しい、最後のトリガーにエラーがあるため、ソフトウェアはログインエラーを出します。
ここで試みられているアプローチ(つまり、トリガー内でトランザクションを操作する)をnot主張していますが、少なくともトリガーがどのように機能するかを明確に理解できるように、何が起こっているのかを説明したいと思いました。
トリガーの基本的な動作は次のとおりです。
@@TRANCOUNT
== 0の場合、トリガーで@@TRANCOUNT
は1になります@@TRANCOUNT
> 0の場合、@@TRANCOUNT
はトリガー内の同じ値になります@@TRANCOUNT
を実行の前後で同じにする必要があるストアドプロシージャとは異なり、トリガーでは、実行後に@@TRANCOUNT
を高くしたり低くしたりできますが、1つの例外があります(次の項目を参照)。@@TRANCOUNT
が0で終了する場合(トリガーが終了する前)、COMMIT
またはROLLBACK
によってトランザクションが終了すると、inserted
およびdeleted
テーブルには行がなくなります。トランザクションの終了後にそれらから何かが必要な場合は、変数または一時テーブル(またはテーブル変数)にキャプチャする必要がありますbeforeトランザクションが終了します。XACT_ABORT ON
で実行されます。 TRY...CATCH
構文がある場合でも、これにより状況が複雑になる可能性があります。場合によっては、XACT_ABORT OFF
を明示的に設定する必要があります。とはいえ、トリガー内のトランザクションは確かにcanCOMMIT
/ROLLBACK
です。簡単に言うと、これらのアクションのいずれかを実行する場合は、トリガーが終了する前にBEGIN TRAN;
を発行するだけで、バッチの中止に関するエラーが発生しません。これは、ROLLBACK
の場合、ROLLBACK
が操作全体をキャンセルできるようにするのではなく、これが発生したという事実をマスクすることを想定しています。これは、トリガー内で通常使用される方法です。
ただし、現実はそれほど単純ではありません。
トリガーの起動前にすでにアクティブなトランザクションが存在する可能性を考慮するには、先頭で@@TRANCOUNT
をキャプチャしてから、正しい数のBEGIN TRAN;
、および場合によってはCOMMIT TRAN;
ステートメントを発行する必要があります。
DECLARE @TranCount INT = @@TRANCOUNT,
@Index INT = 0;
...
-- For an actual COMMIT, do the following:
WHILE (@Index < @TranCount)
BEGIN;
COMMIT TRAN;
SET @Index += 1;
END;
....
-- If either COMMIT or ROLLBACK occurs, do the following:
SET @Index = 0; -- reset in case COMMIT was processed above
WHILE (@Index < @TranCount)
BEGIN;
BEGIN TRAN;
SET @Index += 1;
END;
繰り返しますが、thisはnot推奨ですこれは、既存のトランザクションの性質を変更して、予期しない、直感的でない動作をさせるためです。フローは次のようになります。
COMMIT
またはROLLBACK
で引き続き使用できますこれらすべての最も明確なリファレンスは、Erland Sommarskogによる SQL Serverでのエラーおよびトランザクション処理 です。
CREATE TABLE USERS(ID INT, NAME VARCHAR(10), ROLE VARCHAR(10), ENABLED INT); INSERT INTO USERS VALUES (1, 'USER1', 'ADMIN', 1), (2, 'USER2', 'USER', 1); GO
2行が影響を受けました
CREATE TRIGGER TRG_INS_OF_UPD ON USERS INSTEAD OF UPDATE AS BEGIN IF EXISTS (SELECT 1 FROM inserted WHERE ROLE = 'ADMIN') BEGIN --ROLLBACK TRANSACTION; UPDATE USERS SET ENABLED=0 WHERE ID IN (SELECT ID FROM inserted WHERE ROLE = 'ADMIN'); END -- commit other updates UPDATE u SET ID = i.ID, NAME = i.NAME, ROLE = i.ROLE, ENABLED = i.ENABLED FROM USERS u JOIN inserted i ON i.ID = u.ID WHERE i.ROLE <> 'ADMIN'; END GO
✓
UPDATE USERS SET NAME='ME'; GO
4行が影響を受けました
SELECT * FROM USERS; GO
ID | NAME |ロール|有効 -:| :- :---- | ------: 1 | USER1 |管理| 0 2 |私|ユーザー| 1
dbfiddle ---(ここ
したがって、COMMIT
ステートメントのELSE
は役に立たなかったので、ロールバックしないと、AFTER UPDATE
を使用したので、Updateが実行されます。
COMMIT
を削除すると、通常の状況ではトリガーはエラーを発生させません。