您可以使用CHECKSUM()
一种非常简单的方法来比较实际值,以查看它们是否已更改。 CHECKSUM()
会在传入值列表中生成一个校验和,其值和类型不确定。当心,比较这样的校验和的机会很小,这将导致假阴性。如果您无法处理,则可以HASHBYTES
改用1。
以下示例仅在或列中的任何一个值更改时,才使用AFTER UPDATE
触发器保留对TriggerTest
表所做的修改的历史记录。如果更改,则不采取任何措施。Data1
Data2
Data3
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
如果您坚持使用COLUMNS_UPDATED()函数,则由于表定义可能会更改,这可能会使硬编码的值无效,因此不应硬编码所讨论的列的序数值。您可以使用系统表计算运行时的值。请注意,COLUMNS_UPDATED()
如果在受该语句影响的ANY行中修改了该列,则该函数将为给定的列位返回true 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;
此演示将在历史记录表中插入可能不应该插入的行。这些行Data1
的某些列的Data3
列已更新,并且某些行的列已更新。由于这是一条语句,因此所有行都将通过触发器进行一次处理。由于某些行已Data1
更新(这是COLUMNS_UPDATED()
比较的一部分),因此触发器看到的所有行都将插入到TriggerHistory
表中。如果这对于您的方案是“不正确的”,则可能需要使用游标分别处理每一行。
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;
该TriggerResult
表现在具有一些可能令人误解的行,因为它们绝对不显示任何更改(对该表中的两列),因此它们似乎不属于这些行。在下图中的第二行集合中,TriggerTestID 7是唯一看起来像被修改的行。其他行仅Data3
更新了该列;然而,由于在一个批次中的行已经Data1
更新,所有的行插入TriggerResult
表中。
另外,正如@AaronBertrand和@srutzky指出的那样,您可以对inserted
和deleted
虚拟表中的实际数据进行比较。由于两个表的结构相同,因此可以EXCEPT
在触发器中使用子句来捕获您感兴趣的精确列已更改的行:
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-请参阅/programming/297960/hash-collision-what-are-the-chances,以讨论HASHBYTES计算也可能导致碰撞的可能性极小。 Preshing也对此问题进行了不错的分析。
SET
列表中是否提到了这些列,或者值实际上是否已更改?双方UPDATE
并COLUMNS_UPDATED()
只能告诉你前者。如果你想知道,如果实际值发生变化,你需要做的正确的比较inserted
和deleted
。