我有一个关于TSQL函数更新的问题.例如,我有一个带有字段名称的表.如果我在After Update触发器中检查是否更改了字段名称,请执行以下操作:
if Update(Name) Begin -- process End
即使名称未更改,更新是否仍会返回TRUE?以下更新语句将使用相同的值更新它:
SELECT @v_Name = Name From MyTable Where Id = 1; Update MyTable Set Name = @v_Name where Id = 1;
如果Update()返回TRUE,即使Name的值没有改变,我是否必须比较插入和删除的虚拟表中的值,以确定该值是否真的改变了?
顺便说一句,插入和删除是虚拟表,如果一个TSQL INSERT或UPDATE语句更改了多行数据,它们可能包含多行数据.如果有多个记录,插入和删除的虚拟表中的行数是否相同,更新(名称)的真正含义是什么?这是否意味着至少有一个被改变了?或者更新(名称)是否表示Name的字段已由Update语句设置,无论该值是否已更改?
我使用的SQL服务器是Microsoft SQL 2005.
触发器很棘手,你需要在创建时大量思考.触发器会为每个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
请注意,对于此SQL语句,UPDATE(SampleName)始终为true,无论之前的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语句中.在这种情况下,在触发器中使用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(...子句并将逻辑放在IF语句中,如果存在已更改的行可能有效.即使只有部分行实际存在,该方法也会触发包含在触发器中的所有行)已更改(可能适合您的解决方案,但如果您只想对值更改的行执行某些操作,则可能也不合适.)如果您需要对发生实际更改的行执行某些操作,则需要使用不同的逻辑在他提供的SQL中.
在上面的示例中,当发出UPDATE tblSample SET SampleName ='Foo'语句并且第四行已经是'foo'时,使用GBN的方法更新"last changed datetime"列也会更新第四行,这不会在这种情况下是适当的.
UPDATE()
可以是真的,即使它是相同的值.我不会亲自依赖它,而是会比较价值观.
第二,DELETED
并且INSERTED
具有相同的行数.
Update()函数不是每行,而是跨所有行.不使用它的另一个原因.
在MSDN中更多,但它确实有点稀疏.
评论后:
IF EXISTS ( SELECT * FROM INSERTED I JOIN DELETED D ON I.key = D.key WHERE D.valuecol <> I.valuecol --watch for NULLs! ) blah
我同意确定列值是否实际更改(而不是使用相同值更新)的最佳方法是比较已删除和插入的伪表中的列值.但是,如果要检查多个列,这可能会非常痛苦.
这是我在维护的一些代码中遇到的一个技巧(不知道原作者):使用UNION和带有HAVING子句的GROUP BY来确定哪些列已更改.
例如,在触发器中,获取已更改的行的ID:
SELECT SampleID FROM ( SELECT SampleID, SampleName FROM deleted -- NOTE: UNION, not UNION ALL. UNION by itself removes duplicate -- rows. UNION ALL includes duplicate rows. UNION SELECT SampleID, SampleName FROM inserted ) x GROUP BY SampleID HAVING COUNT(*) > 1
当您只检查单个列是否已更改时,这工作太多了.但是如果你要检查10或20列,那么UNION方法的工作要少得多
WHERE COALESCE(Inserted.Column1, '') <> COALESCE(Deleted.Column1, '') OR COALESCE(Inserted.Column2, '') <> COALESCE(Deleted.Column2, '') OR COALESCE(Inserted.Column3, '') <> COALESCE(Deleted.Column3, '') OR ...