Po naprawieniu wyzwalacza tak, aby obejmował wszystkie trzy operacje,
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
Inną alternatywą są trzy oddzielne wyzwalacze, po jednym dla każdej akcji.
Uważaj jednak na MERGE, jeśli go używasz... Lub przygotuj się na to, gdy przejdziesz na SQL Server 2008 lub nowszy.
EDYTUJ
Myślę, że to, czego możesz szukać, to INSTEAD OF
zamiast tego wyzwalacz (jak na ironię). Oto jeden przykład. Rozważmy bardzo prostą tabelę z kolumną PK i unikalną kolumną:
CREATE TABLE dbo.foobar(id INT PRIMARY KEY, x CHAR(1) UNIQUE);
GO
I prosta tabela dziennika do przechwytywania aktywności:
CREATE TABLE dbo.myLog
(
foobar_id INT,
oldValue XML,
newValue XML,
[action] CHAR(6),
success BIT
);
GO
Następujące INSTEAD OF
wyzwalacz przechwyci INSERT/UPDATE/DELETE
poleceń, próbuj powtórzyć pracę, którą wykonali, i zarejestruj, czy była to porażka, czy sukces:
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
Wypróbujmy kilka bardzo prostych, niejawnych instrukcji dotyczących transakcji:
-- 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
Sprawdź dziennik:
SELECT foobar_id, oldValue, newValue, action, success FROM dbo.myLog;
Wyniki:
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
Oczywiście prawdopodobnie potrzebujesz innych kolumn w tabeli dziennika, takich jak użytkownik, data/godzina, może nawet oryginalne oświadczenie. Nie miało to być w pełni kompleksowe rozwiązanie audytowe, tylko przykład.
Jak wskazuje Mikael, opiera się to na fakcie, że zewnętrzna partia to pojedyncze polecenie, które rozpoczyna niejawną transakcję. Zachowanie będzie musiało zostać przetestowane, jeśli zewnętrzna partia jest jawną transakcją zawierającą wiele instrukcji.
Należy również zauważyć, że nie przechwytuje to „awarii” w przypadku, gdy, powiedzmy, UPDATE wpływa na zero wierszy. Musisz więc wyraźnie zdefiniować, co oznacza "awaria" - w niektórych przypadkach może być konieczne zbudowanie własnej obsługi błędów w zewnętrznym kodzie, a nie w wyzwalaczu.