Dopo aver corretto il trigger per coprire tutte e tre le operazioni,
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
Un'altra alternativa sono tre trigger separati, uno per ogni azione.
Fai attenzione a MERGE se lo stai utilizzando... Oppure preparati quando passi a SQL Server 2008 o versioni successive.
MODIFICA
Penso che quello che potresti cercare sia un INSTEAD OF
innescare invece (che ironia). Ecco un esempio. Consideriamo una tabella molto semplice con una colonna PK e una colonna univoca:
CREATE TABLE dbo.foobar(id INT PRIMARY KEY, x CHAR(1) UNIQUE);
GO
E una semplice tabella di registro per rilevare l'attività:
CREATE TABLE dbo.myLog
(
foobar_id INT,
oldValue XML,
newValue XML,
[action] CHAR(6),
success BIT
);
GO
Il seguente INSTEAD OF
il trigger intercetterà INSERT/UPDATE/DELETE
comandi, tentare di replicare il lavoro che avrebbero svolto e registrare se si è verificato un errore o un successo:
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
Proviamo alcune semplici istruzioni di transazione implicita:
-- 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
Controlla il registro:
SELECT foobar_id, oldValue, newValue, action, success FROM dbo.myLog;
Risultati:
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
Ovviamente probabilmente vorrai altre colonne sulla tabella di registro, come utente, data/ora, forse anche l'istruzione originale. Questa non doveva essere una soluzione di auditing completa, solo un esempio.
Come sottolinea Mikael, questo si basa sul fatto che il batch esterno è un singolo comando che avvia una transazione implicita. Il comportamento dovrà essere testato se il batch esterno è una transazione esplicita con più istruzioni.
Si noti inoltre che questo non acquisisce "errore" nel caso in cui, ad esempio, un UPDATE influisca su zero righe. Quindi è necessario definire in modo esplicito cosa significa "fallimento" - in alcuni casi potrebbe essere necessario creare la propria gestione degli errori nel codice esterno, non in un trigger.