J'ai une table avec 42 colonnes et un déclencheur qui devrait faire des choses lorsque 38 de ces colonnes sont mises à jour. Donc, je dois sauter la logique si les 4 autres colonnes sont modifiées.
Je peux utiliser la fonction PDATE () et créer une grande condition IF
, mais je préfère faire quelque chose de plus court. Utilisation de COLUMNS_UPDATED Je peux vérifier si toutes certaines colonnes sont mises à jour?
Par exemple, vérifier si les colonnes 3, 5 et 9 sont mises à jour:
IF
(
(SUBSTRING(COLUMNS_UPDATED(),1,1) & 20 = 20)
AND
(SUBSTRING(COLUMNS_UPDATED(),2,1) & 1 = 1)
)
PRINT 'Columns 3, 5 and 9 updated';
Donc, valeur 20
pour la colonne 3
et 5
et valeur 1
pour la colonne 9
car il est défini dans le premier bit du deuxième octet. Si je change l'instruction en OR
, elle vérifiera si les colonnes 3
et 5
ou colonne 9
est/sont mis à jour?
Comment appliquer la logique OR
dans le contexte d'un octet?
Vous pouvez utiliser CHECKSUM()
comme 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 peu de 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 HASHBYTES
à la place1.
L'exemple ci-dessous utilise un déclencheur AFTER UPDATE
Pour conserver un historique des modifications apportées à la table TriggerTest
uniquement si l'une des valeurs du Data1
ou Les colonnes Data2
changent. Si Data3
Change, 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 la valeur codée en dur ( s). Vous pouvez calculer la valeur à exécuter à l'aide des tables système. Sachez que la fonction COLUMNS_UPDATED()
renvoie true pour le bit de colonne donné si la colonne est modifiée dans [~ # ~] any [~ # ~] ligne affectée par l'instruction UPDATE TABLE
.
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 colonne Data1
Mise à jour pour certaines lignes et la colonne Data3
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. Comme certaines lignes ont mis à jour Data1
, Qui fait partie de la comparaison COLUMNS_UPDATED()
, toutes les lignes vues par le déclencheur sont insérées dans la table TriggerHistory
. 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;
La table TriggerResult
a maintenant des lignes potentiellement trompeuses qui semblent ne pas appartenir puisqu'elles ne montrent absolument aucune modification (aux deux colonnes de cette table). Dans le deuxième 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 colonne Data3
Mise à jour; cependant, puisque la ligne d'une du lot avait mis à jour Data1
, toutes des lignes sont insérées dans la table TriggerResult
.
Alternativement, comme l'ont souligné @AaronBertrand et @srutzky, 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 clause EXCEPT
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 https://stackoverflow.com/questions/297960/hash-collision-what-are-the-chances pour une analyse de la possibilité infiniment petite que le calcul HASHBYTES puisse également entraîner des collisions . Preshing a également une analyse décente de ce problème.