Ви можете використовувати 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()
функція повертає істину для даного біта стовпця, якщо стовпець змінено у БУДЬ-якому рядку, на який впливає 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 - див. Https://stackoverflow.com/questions/297960/hash-collision-what-are-зміни для обговорення зникаючого малого шансу, що розрахунок HASHBYTES також може спричинити зіткнення. Preshing також має гідний аналіз цієї проблеми.
SET
списку, чи значення фактично змінилися? І те,UPDATE
іCOLUMNS_UPDATED()
тільки вам скажуть колишнє. Якщо ви хочете знати, чи змінилися значення насправді, вам потрібно буде правильно порівнятиinserted
таdeleted
.