更新后触发并更新到 SQL Server 中的行

Posted

技术标签:

【中文标题】更新后触发并更新到 SQL Server 中的行【英文标题】:Trigger and update to a row in SQL Server after it's been updated 【发布时间】:2012-09-17 15:18:53 【问题描述】:

这是简单跟踪数据库行更改的最佳方式吗:

ALTER TRIGGER [dbo].[trg_121s] 
ON  [dbo].[121s]
  AFTER UPDATE
AS 
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

-- Insert statements for trigger here

update dbo.[121s]
set modified=getdate()
where id in 
(select distinct ID from Inserted)

END

因此,对 [121s] 中行的任何更新都会导致 modified 列被更新。

它有效,但我不确定这是否是实现这一目标的最佳方式。

我对这条线有点困惑:

(select distinct ID from Inserted)

...以及它如何知道自己获得了正确的行 ID。

感谢您的任何确认/澄清,

标记

【问题讨论】:

在查询中使用 GetDate() 会追逐移动的目标,会影响性能,并且可能会产生奇怪的结果,例如随着日期的变化。在变量中捕获当前日期/时间然后根据需要使用该值几乎总是一个更好的主意。这在多个语句中更为重要,就像在存储过程中一样。多次使用GetDate() 的最常见原因是捕获长时间运行操作的开始和结束时间。 @HABO 除非所有行都用完全相同的更新时间戳标记是绝对关键的,否则我认为在这种情况下没有太大的问题。 我不确定,但我认为在声明中 getdate() 将为每一行返回相同的日期/时间。 @DumitrescuBogdan 我相当肯定在某些情况下这是不正确的,并且每行都会重新评估,但我承认我没有测试过这是否是其中之一。 @AaronBertrand 不理解我的意思,每当我写一个 sp 时,我总是从定义一些常量(比如时间)开始。但是我还没有看到在选择/更新/插入中,与 getdate() 不同的日期。正如我所说,我不确定。我会调查的。 【参考方案1】:

inserted 是一个伪表,它肯定包含受UPDATE 语句影响的所有正确行(我假设DISTINCT 不是必需的,如果ID 是主键 - 虽然它是很难用121s 之类的名称来分辨表是什么)。在应用修改后的日期/时间之前,您可以考虑验证它们是否真的有更改 值。除此之外,我可能会这样做:

ALTER TRIGGER [dbo].[trg_121s] 
ON [dbo].[121s]
AFTER UPDATE
AS 
BEGIN
  SET NOCOUNT ON;

  UPDATE t SET modified = CURRENT_TIMESTAMP
   FROM dbo.[121s] AS t
   WHERE EXISTS (SELECT 1 FROM inserted WHERE ID = t.ID);
   -- WHERE EXISTS is same as INNER JOIN inserted AS i ON t.ID = i.ID;
END
GO

如果您想要 100% 万无一失地保证它们都使用相同的时间戳进行更新(尽管我不知道我是否曾在此用例中看到过多个值):

ALTER TRIGGER [dbo].[trg_121s] 
ON [dbo].[121s]
AFTER UPDATE
AS 
BEGIN
  SET NOCOUNT ON;

  DECLARE @ts DATETIME;
  SET @ts = CURRENT_TIMESTAMP;

  UPDATE t SET modified = @ts
   FROM dbo.[121s] AS t
  INNER JOIN inserted AS i 
  ON t.ID = i.ID;
END
GO

如果您想确保更新仅在 foo 列更改值时发生,您可以说:

  UPDATE t SET modified = @ts
   FROM dbo.[121s] AS t
   INNER JOIN inserted AS i
   ON t.ID = i.ID
   AND t.foo <> i.foo;

这是一般模式,但如果 foo 可以为空,则它会变得更加复杂,因为 SQL Server 将无法匹配一侧有值而另一侧没有(或两者都没有)的行)。在这种情况下,你会这样做:

   AND 
   (
     t.foo <> i.foo
     OR (t.foo IS NULL AND i.foo IS NOT NULL)
     OR (t.foo IS NOT NULL AND i.foo IS NULL)
   );

有些人会这样说“我可以使用 COALESCE 或 ISNULL 来对抗一些魔法值”:

WHERE COALESCE(t.foo, 'magic') <> COALESCE(i.foo, 'magic')

...我会警告你不要这样做,因为你会不断地寻找一些在数据中不存在的神奇值。

【讨论】:

谢谢您-我已将此标记为答案,从您的原始评论中我知道您必须立即开始处理它。我也非常感谢其他答案,并为他们 +1 - 所以谢谢大家 - 这里的帮助令人难以置信! 如果您不介意,只是一个快速查询以扩展您的答案...我确实看到了另一个更新时间戳的参考,仅当某些列(可能不仅仅是一个,如在您的示例中)已更改:如果 update([name]) 或 update([address]) begin.... 这可能吗? (对于信息 121s 是与员工进行一对一会议的表格,以提供有关绩效等的最新信息) - 再次感谢,马克 @fixit 不,你不能依赖它。如果我说UPDATE foo SET bar = barbar 已经是1 并且我说UPDATE foo SET bar = 1 那么UPDATE(bar) 将返回true,即使我没有更改值。 谢谢 - 如果您确实想检查两列,您会设置两个触发器吗? @CervEd 不,这只是我 9 年前的个人风格偏好。【参考方案2】:

Inserted 是一个表,其中包含受触发触发器(插入/更新)的操作影响的行。所以你这里的触发器是正确的。如果 Id 是主键,那么您不需要 distinct(从 Inserted 中选择 id 就足够了)。如果 Id 不是主键,那么您的触发器是错误的,因为您最终可能会更新比您应该更新的更多。

【讨论】:

【参考方案3】:

虽然很多人,不仅在 *** 上,而且在世界各地都会告诉你触发器是邪恶的,但我要说的是,当它们没有被滥用时,它们是有价值的。在这里,如果您想使用此触发器,它似乎是有效的,并且 Inserted 表包含由触发此触发器的语句更新的行 - 它是正确的,除了 DISTINCT 可能被删除,如果 ID 是主键。

但是,如果您有灵活性,另一种选择是改用 timestamp 列。但是,不要混淆,timestamp与日期和时间相关。因此,如果您需要日期和时间,请坚持使用现有的内容。

【讨论】:

使用时间戳实际上与我的建议相反。通常人们必须远离时间戳,因为他们真正想要的是日期/时间,而不是一些无法帮助他们追踪行被修改的不可读的 rowversion 二进制值。 我知道时间戳 - 我认为它只是措辞不当 - 我知道它在未来会被贬值。

以上是关于更新后触发并更新到 SQL Server 中的行的主要内容,如果未能解决你的问题,请参考以下文章

更新触发器后的 SQL Server

如何在 SQL Server 触发器中复制插入、更新、删除的行

插入后使用 SQL Server 触发器更新另一个表中的列

创建触发器后的 SQL Server

插入和更新后未触发 SQL Server 触发器

在 Microsoft SQL Server 上插入触发器后 -- 更新新创建记录中的列