got*_*tqn 14 trigger sql-server t-sql
我有一个包含 42 列的表和一个触发器,当这些列中的 38 列更新时,它应该做一些事情。因此,如果其余 4 列发生更改,我需要跳过逻辑。
我可以使用UPDATE()函数并创建一个大IF条件,但更喜欢做一些更短的事情。使用COLUMNS_UPDATED我可以检查是否所有某些列都更新了?
例如,检查第 3、5 和 9 列是否更新:
IF
(
(SUBSTRING(COLUMNS_UPDATED(),1,1) & 20 = 20)
AND
(SUBSTRING(COLUMNS_UPDATED(),2,1) & 1 = 1)
)
PRINT 'Columns 3, 5 and 9 updated';
Run Code Online (Sandbox Code Playgroud)
因此,20列3和的5值,以及1列的值,9因为它设置在第二个字节的第一位。如果我将语句更改为OR它会检查列3和/5或列9是否更新?
如何OR在一个字节的上下文中应用逻辑?
Han*_*non 19
您可以使用CHECKSUM()一种相当简单的方法来比较实际值以查看它们是否已更改。 CHECKSUM()将在传入值的列表中生成校验和,其中的数量和类型是不确定的。请注意,像这样比较校验和的可能性很小,会导致误报。如果您无法处理,则可以使用1HASHBYTES代替。
下面的示例使用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
Run Code Online (Sandbox Code Playgroud)
如果您坚持使用COLUMNS_UPDATED() 函数,则不应硬编码相关列的序号值,因为表定义可能会更改,这可能会使硬编码值无效。您可以使用系统表计算运行时的值。请注意,COLUMNS_UPDATED()如果在受语句影响的任何行中修改了列,则该函数将为给定的列位返回 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;
Run Code Online (Sandbox Code Playgroud)
--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;
Run Code Online (Sandbox Code Playgroud)
--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
Run Code Online (Sandbox Code Playgroud)
--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;
Run Code Online (Sandbox Code Playgroud)
此演示将可能不应插入的行插入到历史记录表中。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;
Run Code Online (Sandbox Code Playgroud)
该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
Run Code Online (Sandbox Code Playgroud)
1 - 请参阅/sf/ask/20857231/以讨论 HASHBYTES 计算也可能导致冲突的可能性很小。 Preshing对这个问题也有很好的分析。