3 つの操作すべてをカバーするようにトリガーを修正したら、
IF EXISTS (SELECT 1 FROM inserted)
BEGIN
IF EXISTS (SELECT 1 FROM deleted)
BEGIN
SET @action = 'UPDATE';
END
ELSE
BEGIN
SET @action = 'INSERT';
END
ELSE
BEGIN
SET @action = 'DELETE';
END
もう 1 つの方法は、アクションごとに 1 つずつ、3 つの個別のトリガーです。
MERGE
ただし、それを使用している場合は注意してください... または、SQL Server 2008 以降に移行するときに備えてください。
編集
代わりに、あなたが求めているのは引き金だと思いますINSTEAD OF
(なんて皮肉なことでしょう)。ここに一例があります。PK 列と一意の列を持つ非常に単純なテーブルを考えてみましょう。
CREATE TABLE dbo.foobar(id INT PRIMARY KEY, x CHAR(1) UNIQUE);
GO
そして、アクティビティをキャッチするための単純なログ テーブル:
CREATE TABLE dbo.myLog
(
foobar_id INT,
oldValue XML,
newValue XML,
[action] CHAR(6),
success BIT
);
GO
次のINSTEAD OF
トリガーはINSERT/UPDATE/DELETE
、コマンドをインターセプトし、コマンドが実行するはずだった作業を複製しようとし、失敗か成功かをログに記録します。
CREATE TRIGGER dbo.foobar_inst
ON dbo.foobar
INSTEAD OF INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @action CHAR(6), @success BIT;
SELECT @action = 'DELETE', @success = 1;
IF EXISTS (SELECT 1 FROM inserted)
BEGIN
IF EXISTS (SELECT 1 FROM deleted)
SET @action = 'UPDATE';
ELSE
SET @action = 'INSERT';
END
BEGIN TRY
IF @action = 'INSERT'
INSERT dbo.foobar(id, x) SELECT id, x FROM inserted;
IF @action = 'UPDATE'
UPDATE f SET x = i.x FROM dbo.foobar AS f
INNER JOIN inserted AS i ON f.id = i.id;
IF @action = 'DELETE'
DELETE f FROM dbo.foobar AS f
INNER JOIN inserted AS i ON f.id = i.id;
END TRY
BEGIN CATCH
ROLLBACK; -- key part here!
SET @success = 0;
END CATCH
IF @action = 'INSERT'
INSERT dbo.myLog SELECT i.id, NULL,
(SELECT * FROM inserted WHERE id = i.id FOR XML PATH),
@action, @success FROM inserted AS i;
IF @action = 'UPDATE'
INSERT dbo.myLog SELECT i.id,
(SELECT * FROM deleted WHERE id = i.id FOR XML PATH),
(SELECT * FROM inserted WHERE id = i.id FOR XML PATH),
@action, @success FROM inserted AS i;
IF @action = 'DELETE'
INSERT dbo.myLog SELECT d.id,
(SELECT * FROM deleted WHERE id = d.id FOR XML PATH),
NULL, @action, @success FROM deleted AS d;
END
GO
非常に単純な暗黙のトランザクション ステートメントをいくつか試してみましょう。
-- these succeed:
INSERT dbo.foobar SELECT 1, 'x';
GO
INSERT dbo.foobar SELECT 2, 'y';
GO
-- fails with PK violation:
INSERT dbo.foobar SELECT 1, 'z';
GO
-- fails with UQ violation:
UPDATE dbo.foobar SET x = 'y' WHERE id = 1;
GO
ログを確認します。
SELECT foobar_id, oldValue, newValue, action, success FROM dbo.myLog;
結果:
foobar_id oldValue newValue action success
--------- ----------------------------- ----------------------------- ------ -------
1 NULL <row><id>1</id><x>x</x></row> INSERT 1
2 NULL <row><id>2</id><x>y</x></row> INSERT 1
1 NULL <row><id>1</id><x>z</x></row> INSERT 0
1 <row><id>1</id><x>x</x></row> <row><id>1</id><x>y</x></row> UPDATE 0
もちろん、ユーザー、日付/時刻、場合によっては元のステートメントなど、ログ テーブルに他の列が必要になることもあります。これは、完全に包括的な監査ソリューションを意図したものではなく、単なる例です。
Mikael が指摘するように、これは外側のバッチが暗黙的なトランザクションを開始する単一のコマンドであるという事実に依存しています。外側のバッチが明示的な複数ステートメントのトランザクションである場合、動作をテストする必要があります。
また、たとえば、UPDATE がゼロ行に影響する場合、これは「失敗」をキャプチャしないことに注意してください。そのため、「失敗」の意味を明示的に定義する必要があります。場合によっては、トリガーではなく、外部コードで独自の失敗処理を構築する必要があります。