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 HASHBYTESinvece 1 .
L'esempio seguente utilizza un AFTER UPDATEtrigger per conservare una cronologia delle modifiche apportate alla TriggerTesttabella solo se uno dei valori nelle colonne Data1 o Data2 cambia. Se Data3cambia, 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 TABLEdall'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 Data1colonna aggiornata per alcune righe e la Data3colonna è 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 Data1aggiornate, il che fa parte del COLUMNS_UPDATED()confronto, tutte le righe visualizzate dal trigger vengono inserite nella TriggerHistorytabella. 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 TriggerResulttabella 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 Data3colonna aggiornata; tuttavia poiché la prima riga nel batch è stata Data1aggiornata, tutte le righe vengono inserite nella TriggerResulttabella.

In alternativa, come sottolineato da @AaronBertrand e @srutzky, è possibile eseguire un confronto dei dati effettivi nelle tabelle virtuali insertede deleted. Poiché la struttura di entrambe le tabelle è identica, puoi utilizzare una EXCEPTclausola 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.
SETnell'elenco o se i valori sono effettivamente cambiati? EntrambiUPDATEeCOLUMNS_UPDATED()ti dico solo il primo. Se vuoi sapere se i valori sono effettivamente cambiati, dovrai fare un confronto adeguato diinsertededeleted.