Puoi utilizzare CHECKSUM()
una metodologia abbastanza semplice per confrontare i valori reali per vedere se sono stati modificati. CHECKSUM()
genererà un checksum attraverso un elenco di valori passati, di cui il numero e il tipo sono indeterminati. Attenzione, c'è una piccola possibilità che il confronto di checksum come questo comporterà falsi negativi. Se non puoi gestirlo, puoi usare HASHBYTES
invece 1 .
L'esempio seguente utilizza un AFTER UPDATE
trigger per conservare una cronologia delle modifiche apportate alla TriggerTest
tabella solo se uno dei valori nelle colonne Data1
o Data2
cambia. Se Data3
cambia, non viene intrapresa alcuna azione.
USE tempdb;
IF COALESCE(OBJECT_ID('dbo.TriggerTest'), 0) <> 0
BEGIN
DROP TABLE dbo.TriggerTest;
END
CREATE TABLE dbo.TriggerTest
(
TriggerTestID INT NOT NULL
CONSTRAINT PK_TriggerTest
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, Data1 VARCHAR(10) NULL
, Data2 VARCHAR(10) NOT NULL
, Data3 DATETIME NOT NULL
);
IF COALESCE(OBJECT_ID('dbo.TriggerResult'), 0) <> 0
BEGIN
DROP TABLE dbo.TriggerResult;
END
CREATE TABLE dbo.TriggerResult
(
TriggerTestID INT NOT NULL
, Data1OldVal VARCHAR(10) NULL
, Data1NewVal VARCHAR(10) NULL
, Data2OldVal VARCHAR(10) NULL
, Data2NewVal VARCHAR(10) NULL
);
GO
IF COALESCE(OBJECT_ID('dbo.TriggerTest_AfterUpdate'), 0) <> 0
BEGIN
DROP TRIGGER TriggerTest_AfterUpdate;
END
GO
CREATE TRIGGER TriggerTest_AfterUpdate
ON dbo.TriggerTest
AFTER UPDATE
AS
BEGIN
INSERT INTO TriggerResult
(
TriggerTestID
, Data1OldVal
, Data1NewVal
, Data2OldVal
, Data2NewVal
)
SELECT d.TriggerTestID
, d.Data1
, i.Data1
, d.Data2
, i.Data2
FROM inserted i
LEFT JOIN deleted d ON i.TriggerTestID = d.TriggerTestID
WHERE CHECKSUM(i.Data1, i.Data2) <> CHECKSUM(d.Data1, d.Data2);
END
GO
INSERT INTO dbo.TriggerTest (Data1, Data2, Data3)
VALUES ('blah', 'foo', GETDATE());
UPDATE dbo.TriggerTest
SET Data1 = 'blah', Data2 = 'fee'
WHERE TriggerTestID = 1;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult
Se si insiste sull'uso della funzione COLUMNS_UPDATED () , non è necessario codificare il valore ordinale delle colonne in questione, poiché la definizione della tabella potrebbe cambiare, il che potrebbe invalidare i valori codificati. È possibile calcolare quale valore dovrebbe essere in fase di esecuzione utilizzando le tabelle di sistema. Tenere presente che la COLUMNS_UPDATED()
funzione restituisce true per il bit di colonna specificato se la colonna viene modificata in QUALSIASI riga interessata UPDATE TABLE
dall'istruzione.
USE tempdb;
IF COALESCE(OBJECT_ID('dbo.TriggerTest'), 0) <> 0
BEGIN
DROP TABLE dbo.TriggerTest;
END
CREATE TABLE dbo.TriggerTest
(
TriggerTestID INT NOT NULL
CONSTRAINT PK_TriggerTest
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, Data1 VARCHAR(10) NULL
, Data2 VARCHAR(10) NOT NULL
, Data3 DATETIME NOT NULL
);
IF COALESCE(OBJECT_ID('dbo.TriggerResult'), 0) <> 0
BEGIN
DROP TABLE dbo.TriggerResult;
END
CREATE TABLE dbo.TriggerResult
(
TriggerTestID INT NOT NULL
, Data1OldVal VARCHAR(10) NULL
, Data1NewVal VARCHAR(10) NULL
, Data2OldVal VARCHAR(10) NULL
, Data2NewVal VARCHAR(10) NULL
);
GO
IF COALESCE(OBJECT_ID('dbo.TriggerTest_AfterUpdate'), 0) <> 0
BEGIN
DROP TRIGGER TriggerTest_AfterUpdate;
END
GO
CREATE TRIGGER TriggerTest_AfterUpdate
ON dbo.TriggerTest
AFTER UPDATE
AS
BEGIN
DECLARE @ColumnOrdinalTotal INT = 0;
SELECT @ColumnOrdinalTotal = @ColumnOrdinalTotal
+ POWER (
2
, COLUMNPROPERTY(t.object_id,c.name,'ColumnID') - 1
)
FROM sys.schemas s
INNER JOIN sys.tables t ON s.schema_id = t.schema_id
INNER JOIN sys.columns c ON t.object_id = c.object_id
WHERE s.name = 'dbo'
AND t.name = 'TriggerTest'
AND c.name IN (
'Data1'
, 'Data2'
);
IF (COLUMNS_UPDATED() & @ColumnOrdinalTotal) > 0
BEGIN
INSERT INTO TriggerResult
(
TriggerTestID
, Data1OldVal
, Data1NewVal
, Data2OldVal
, Data2NewVal
)
SELECT d.TriggerTestID
, d.Data1
, i.Data1
, d.Data2
, i.Data2
FROM inserted i
LEFT JOIN deleted d ON i.TriggerTestID = d.TriggerTestID;
END
END
GO
--this won't result in rows being inserted into the history table
INSERT INTO dbo.TriggerTest (Data1, Data2, Data3)
VALUES ('blah', 'foo', GETDATE());
SELECT *
FROM dbo.TriggerResult;
--this will insert rows into the history table
UPDATE dbo.TriggerTest
SET Data1 = 'blah', Data2 = 'fee'
WHERE TriggerTestID = 1;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult;
--this WON'T insert rows into the history table
UPDATE dbo.TriggerTest
SET Data3 = GETDATE()
WHERE TriggerTestID = 1;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult
--this will insert rows into the history table, even though only
--one of the columns was updated
UPDATE dbo.TriggerTest
SET Data1 = 'blum'
WHERE TriggerTestID = 1;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult;
Questa demo inserisce righe nella tabella della cronologia che forse non dovrebbero essere inserite. Le righe hanno avuto la Data1
colonna aggiornata per alcune righe e la Data3
colonna è stata aggiornata per alcune righe. Poiché si tratta di una singola istruzione, tutte le righe vengono elaborate da un singolo passaggio attraverso il trigger. Poiché alcune righe sono state Data1
aggiornate, il che fa parte del COLUMNS_UPDATED()
confronto, tutte le righe visualizzate dal trigger vengono inserite nella TriggerHistory
tabella. Se questo è "errato" per il tuo scenario, potresti dover gestire ogni riga separatamente, usando un cursore.
INSERT INTO dbo.TriggerTest (Data1, Data2, Data3)
SELECT TOP(10) LEFT(o.name, 10)
, LEFT(o1.name, 10)
, GETDATE()
FROM sys.objects o
, sys.objects o1;
UPDATE dbo.TriggerTest
SET Data1 = CASE WHEN TriggerTestID % 6 = 1 THEN Data2 ELSE Data1 END
, Data3 = CASE WHEN TriggerTestID % 6 = 2 THEN GETDATE() ELSE Data3 END;
SELECT *
FROM dbo.TriggerTest;
SELECT *
FROM dbo.TriggerResult;
La TriggerResult
tabella ora ha alcune righe potenzialmente fuorvianti che sembrano non appartenere poiché non mostrano assolutamente alcuna modifica (alle due colonne in quella tabella). Nel secondo set di righe nell'immagine seguente, TriggerTestID 7 è l'unico che sembra sia stato modificato. Le altre righe avevano solo la Data3
colonna aggiornata; tuttavia poiché la prima riga nel batch è stata Data1
aggiornata, tutte le righe vengono inserite nella TriggerResult
tabella.
In alternativa, come sottolineato da @AaronBertrand e @srutzky, è possibile eseguire un confronto dei dati effettivi nelle tabelle virtuali inserted
e deleted
. Poiché la struttura di entrambe le tabelle è identica, puoi utilizzare una EXCEPT
clausola nel trigger per acquisire righe in cui sono cambiate le colonne precise a cui sei interessato:
IF COALESCE(OBJECT_ID('dbo.TriggerTest_AfterUpdate'), 0) <> 0
BEGIN
DROP TRIGGER TriggerTest_AfterUpdate;
END
GO
CREATE TRIGGER TriggerTest_AfterUpdate
ON dbo.TriggerTest
AFTER UPDATE
AS
BEGIN
;WITH src AS
(
SELECT d.TriggerTestID
, d.Data1
, d.Data2
FROM deleted d
EXCEPT
SELECT i.TriggerTestID
, i.Data1
, i.Data2
FROM inserted i
)
INSERT INTO dbo.TriggerResult
(
TriggerTestID,
Data1OldVal,
Data1NewVal,
Data2OldVal,
Data2NewVal
)
SELECT i.TriggerTestID
, d.Data1
, i.Data1
, d.Data2
, i.Data2
FROM inserted i
INNER JOIN deleted d ON i.TriggerTestID = d.TriggerTestID
END
GO
1 - vedi /programming/297960/hash-collision-what-are-the-chances per una discussione sulle possibilità minime che il calcolo di HASHBYTES possa anche causare collisioni. Anche il preshing ha un'analisi decente di questo problema.
SET
nell'elenco o se i valori sono effettivamente cambiati? EntrambiUPDATE
eCOLUMNS_UPDATED()
ti dico solo il primo. Se vuoi sapere se i valori sono effettivamente cambiati, dovrai fare un confronto adeguato diinserted
edeleted
.