Vous pouvez utiliser CHECKSUM()
une méthodologie assez simple pour comparer les valeurs réelles pour voir si elles ont été modifiées. CHECKSUM()
générera une somme de contrôle sur une liste de valeurs transmises, dont le nombre et le type sont indéterminés. Attention, il y a une petite chance de comparer des sommes de contrôle comme celle-ci entraînera de faux négatifs. Si vous ne pouvez pas gérer cela, vous pouvez utiliser à la HASHBYTES
place 1 .
L'exemple ci-dessous utilise un AFTER UPDATE
déclencheur pour conserver un historique des modifications apportées à la TriggerTest
table uniquement si l'une des valeurs des colonnes Data1
ou Data2
change. En cas de Data3
changement, aucune action n'est entreprise.
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
Si vous insistez pour utiliser la fonction COLUMNS_UPDATED () , vous ne devez pas coder en dur la valeur ordinale des colonnes en question, car la définition de la table peut changer, ce qui peut invalider les valeurs codées en dur. Vous pouvez calculer quelle devrait être la valeur lors de l'exécution à l'aide des tables système. N'oubliez pas que la COLUMNS_UPDATED()
fonction renvoie true pour le bit de colonne donné si la colonne est modifiée dans N'IMPORTE QUELLE ligne affectée par l' UPDATE TABLE
instruction.
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;
Cette démo insère des lignes dans la table d'historique qui ne devraient peut-être pas être insérées. Les lignes ont vu leur Data1
colonne mise à jour pour certaines lignes et la Data3
colonne a été mise à jour pour certaines lignes. Puisqu'il s'agit d'une instruction unique, toutes les lignes sont traitées par un seul passage à travers le déclencheur. Étant donné que certaines lignes ont été Data1
mises à jour, ce qui fait partie de la COLUMNS_UPDATED()
comparaison, toutes les lignes vues par le déclencheur sont insérées dans le TriggerHistory
tableau. Si cela est "incorrect" pour votre scénario, vous devrez peut-être gérer chaque ligne séparément, à l'aide d'un curseur.
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;
Le TriggerResult
tableau a maintenant des lignes potentiellement trompeuses qui semblent n'appartenir à aucun, car elles ne montrent absolument aucun changement (aux deux colonnes de ce tableau). Dans le 2e ensemble de lignes de l'image ci-dessous, TriggerTestID 7 est le seul qui semble avoir été modifié. Les autres lignes n'avaient que la Data3
colonne mise à jour; cependant, comme une ligne du lot a été Data1
mise à jour, toutes les lignes sont insérées dans le TriggerResult
tableau.
Alternativement, comme @AaronBertrand et @srutzky l'ont souligné, vous pouvez effectuer une comparaison des données réelles dans les tables virtuelles inserted
et deleted
. Étant donné que la structure des deux tables est identique, vous pouvez utiliser une EXCEPT
clause dans le déclencheur pour capturer les lignes où les colonnes précises qui vous intéressent ont changé:
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 - voir /programming/297960/hash-collision-what-are-the-chances pour une discussion sur les chances infiniment faibles que le calcul HASHBYTES puisse également entraîner des collisions. Preshing a également une analyse décente de ce problème.
SET
liste ou si les valeurs ont réellement changé? Les deuxUPDATE
etCOLUMNS_UPDATED()
seulement vous dire l'ancien. Si vous voulez savoir si les valeurs ont réellement changé, vous devrez comparer correctementinserted
et etdeleted
.