Sie können CHECKSUM()
eine relativ einfache Methode zum Vergleichen der tatsächlichen Werte verwenden, um festzustellen, ob sie geändert wurden. CHECKSUM()
erzeugt eine Prüfsumme über eine Liste von übergebenen Werten, deren Anzahl und Typ unbestimmt sind. Achtung, es gibt eine kleine Chance, dass ein Vergleich von Prüfsummen zu falschen Negativen führt. Wenn Sie damit nicht umgehen können, können Sie HASHBYTES
stattdessen 1 verwenden .
Im folgenden Beispiel wird ein AFTER UPDATE
Trigger verwendet, um den Verlauf der an der TriggerTest
Tabelle vorgenommenen Änderungen nur dann beizubehalten, wenn sich einer der Werte in der Spalte Data1
oder Data2
ändert. Bei Data3
Änderungen wird keine Aktion ausgeführt.
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
Wenn Sie darauf bestehen, die Funktion COLUMNS_UPDATED () zu verwenden , sollten Sie den Ordinalwert der betreffenden Spalten nicht hartcodieren , da sich die Tabellendefinition ändern kann, wodurch hartcodierte Werte ungültig werden können. Anhand der Systemtabellen können Sie berechnen, wie hoch der Wert zur Laufzeit sein soll. Beachten Sie, dass die COLUMNS_UPDATED()
Funktion für die gegebene Spalte Bit true zurück , wenn die Spalte in modifiziert wird jedem der betroffenen Zeile UPDATE TABLE
Aussage.
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;
Diese Demo fügt Zeilen in die Verlaufstabelle ein, die möglicherweise nicht eingefügt werden sollten. Die Reihen haben ihre hatten Data1
Spalte für einige Zeilen aktualisiert und haben die hatten Data3
Spalte für einige Zeilen aktualisiert. Da es sich um eine einzelne Anweisung handelt, werden alle Zeilen in einem Durchgang durch den Trigger verarbeitet. Da einige Zeilen Data1
aktualisiert wurden, was Teil des COLUMNS_UPDATED()
Vergleichs ist, werden alle vom Trigger angezeigten Zeilen in die TriggerHistory
Tabelle eingefügt . Wenn dies für Ihr Szenario "falsch" ist, müssen Sie möglicherweise jede Zeile einzeln mit einem Cursor behandeln.
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;
Die TriggerResult
Tabelle enthält jetzt einige möglicherweise irreführende Zeilen, die so aussehen, als gehörten sie nicht dazu, da sie absolut keine Änderungen aufweisen (an den beiden Spalten in dieser Tabelle). In der zweiten Reihe von Zeilen im Bild unten ist TriggerTestID 7 die einzige, die so aussieht, als ob sie geändert wurde. In den anderen Zeilen wurde nur die Data3
Spalte aktualisiert. Da jedoch die eine Zeile im Stapel Data1
aktualisiert wurde, werden alle Zeilen in die TriggerResult
Tabelle eingefügt .
Alternativ können Sie, wie @AaronBertrand und @srutzky betonten, einen Vergleich der tatsächlichen Daten in der inserted
und der deleted
virtuellen Tabelle durchführen . Da die Struktur beider Tabellen identisch ist, können Sie eine EXCEPT
Klausel im Trigger verwenden, um Zeilen zu erfassen, in denen sich die genauen gewünschten Spalten geändert haben:
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 - siehe /programming/297960/hash-collision-what-are-the-chances für eine Diskussion der verschwindend geringen Wahrscheinlichkeit, dass die HASHBYTES-Berechnung auch zu Kollisionen führen kann. Preshing hat auch eine anständige Analyse dieses Problems.
SET
Liste aufgeführt sind oder ob sich die Werte tatsächlich geändert haben? BeidesUPDATE
undCOLUMNS_UPDATED()
nur das erstere. Wenn Sie wissen möchten, ob sich die Werte tatsächlich geändert haben, müssen Sie einen ordnungsgemäßen Vergleich voninserted
und durchführendeleted
.