触发器很棘手,您在创建触发器时需要大量思考。每个 UPDATE 语句触发一次触发器。如果该 UPDATE 语句更新多行,则触发器仍只会触发一次。当 UPDATE 语句中包含该列时,UPDATE() 函数对该列返回 true。该功能允许您在更新语句中甚至不包含该列时回避 SQL 逻辑,从而有助于提高触发器的效率。它不会告诉您给定行中列的值是否发生了变化。
这是一个示例表...
CREATE TABLE tblSample
(
SampleID INT PRIMARY KEY,
SampleName VARCHAR(10),
SampleNameLastChangedDateTime DATETIME,
Parent_SampleID INT
)
如果对这个表使用了以下 SQL:
UPDATE tblSample SET SampleName = 'hello'
..并且 AFTER INSERT, UPDATE 触发器生效,这个特定的 SQL 语句将始终按如下方式评估 UPDATE 函数...
IF UPDATE(SampleName) --aways evaluates to TRUE
IF UPDATE(SampleID) --aways evaluates to FALSE
IF UPDATE(Parent_SampleID) --aways evaluates to FALSE
请注意,无论 SampleName 之前的值是什么,该 SQL 语句的 UPDATE(SampleName) 始终为真。它返回 true 是因为 UPDATE 语句在该子句的 SET 部分中包含 SampleName 列,而不是基于之前或之后的值。 UPDATE() 函数不会确定值是否已更改。如果您想根据值是否更改来执行操作,您将需要使用 SQL 并比较插入和删除的行。
以下是使最后更新的列保持同步的方法:
--/*
IF OBJECT_ID('dbo.tgr_tblSample_InsertUpdate', 'TR') IS NOT NULL
DROP TRIGGER dbo.tgr_tblSample_InsertUpdate
GO
--*/
CREATE TRIGGER dbo.tgr_tblSample_InsertUpdate ON dbo.tblSample
AFTER INSERT, UPDATE
AS
BEGIN --Trigger
IF UPDATE(SampleName)
BEGIN
UPDATE tblSample SET
SampleNameLastChangedDateTime = CURRENT_TIMESTAMP
WHERE
SampleID IN (SELECT Inserted.SampleID
FROM Inserted LEFT JOIN Deleted ON Inserted.SampleID = Deleted.SampleID
WHERE COALESCE(Inserted.SampleName, '') <> COALESCE(Deleted.SampleName, ''))
END
END --Trigger
判断行是否被更新的逻辑在上面的 WHERE 子句中。这是您需要做的真正检查。我的逻辑是使用 COALESCE 来处理 NULL 值和 INSERTS。
...
WHERE
SampleID IN (SELECT Inserted.SampleID
FROM Inserted LEFT JOIN Deleted ON Inserted.SampleID = Deleted.SampleID
WHERE COALESCE(Inserted.SampleName, '') <> COALESCE(Deleted.SampleName, ''))
请注意,IF UPDATE() 检查用于帮助提高触发器在 SampleName 列未更新时的效率。例如,如果一条 SQL 语句更新了 Parent_SampleID 列,那么当它不需要运行时,IF UPDATE(SampleName) 检查将有助于避开该 IF 语句中更复杂的逻辑。考虑在适当的时候使用 UPDATE(),但不要出于错误的原因。
还要意识到,根据您的架构,UPDATE 功能可能对您没有用处。如果您的代码体系结构使用的中间层总是在保存对象时使用业务对象中的值更新表行中的所有列,则触发器中的 UPDATE() 函数将变得无用。在这种情况下,您的代码可能总是使用中间层发出的每个 UPDATE 语句来更新所有列。在这种情况下,当您的业务对象被保存时,UPDATE(columnname) 函数将始终评估为 true,因为所有列名始终包含在更新语句中。在这种情况下,在触发器中使用 UPDATE() 并没有帮助,而且在大多数情况下只会在该触发器中产生额外的开销。
这里有一些 SQL 可以使用上面的触发器:
INSERT INTO tblSample
(
SampleID,
SampleName
)
SELECT 1, 'One'
UNION SELECT 2, 'Two'
UNION SELECT 3, 'Three'
GO
SELECT SampleID, SampleName, SampleNameLastChangedDateTime FROM tblSample
/*
SampleID SampleName SampleNameLastChangedDateTime
----------- ---------- -----------------------------
1 One 2010-10-27 14:52:42.567
2 Two 2010-10-27 14:52:42.567
3 Three 2010-10-27 14:52:42.567
*/
GO
INSERT INTO tblSample
(
SampleID,
SampleName
)
SELECT 4, 'Foo'
UNION SELECT 5, 'Five'
GO
SELECT SampleID, SampleName, SampleNameLastChangedDateTime FROM tblSample
/*
SampleID SampleName SampleNameLastChangedDateTime
----------- ---------- -----------------------------
1 One 2010-10-27 14:52:42.567
2 Two 2010-10-27 14:52:42.567
3 Three 2010-10-27 14:52:42.567
4 Foo 2010-10-27 14:52:42.587
5 Five 2010-10-27 14:52:42.587
*/
GO
UPDATE tblSample SET SampleName = 'Foo'
SELECT SampleID, SampleName, SampleNameLastChangedDateTime FROM tblSample
/*
SampleID SampleName SampleNameLastChangedDateTime
----------- ---------- -----------------------------
1 Foo 2010-10-27 14:52:42.657
2 Foo 2010-10-27 14:52:42.657
3 Foo 2010-10-27 14:52:42.657
4 Foo 2010-10-27 14:52:42.587
5 Foo 2010-10-27 14:52:42.657
*/
GO
UPDATE tblSample SET SampleName = 'Not Prime' WHERE SampleID IN (1,4)
SELECT SampleID, SampleName, SampleNameLastChangedDateTime FROM tblSample
/*
SampleID SampleName SampleNameLastChangedDateTime
----------- ---------- -----------------------------
1 Not Prime 2010-10-27 14:52:42.680
2 Foo 2010-10-27 14:52:42.657
3 Foo 2010-10-27 14:52:42.657
4 Not Prime 2010-10-27 14:52:42.680
5 Foo 2010-10-27 14:52:42.657
*/
--Clean up...
DROP TRIGGER dbo.tgr_tblSample_InsertUpdate
DROP TABLE tblSample
用户 GBN 提出以下建议:
IF EXISTS (
SELECT
*
FROM
INSERTED I
JOIN
DELETED D ON I.key = D.key
WHERE
D.valuecol <> I.valuecol --watch for NULLs!
)
blah
GBN 建议使用 IF (EXISTS( ...clause 并在存在已更改行的情况下将逻辑放入该 IF 语句中是可行的。该方法将对触发器中包含的所有行触发,即使只有一些行实际上已更改(这可能适合您的解决方案,但如果您只想对值更改的行执行某些操作,则可能不合适。)如果您需要对发生实际更改的行执行某些操作,您需要他提供的 SQL 中的不同逻辑。
在我上面的示例中,当发出 UPDATE tblSample SET SampleName = 'Foo' 语句并且第四行已经是 'foo' 时,使用 GBN 的方法更新“最后更改的日期时间”列也会更新第四行,在这种情况下不合适。