更新后触发并更新到 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 = bar
或bar
已经是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 中的行的主要内容,如果未能解决你的问题,请参考以下文章