【问题标题】:How to "cascade" update timestamp (using triggers)如何“级联”更新时间戳(使用触发器)
【发布时间】:2014-04-02 23:51:36
【问题描述】:

我有三个表,“元素”表中的行属于“项目”表中的一行,而这些行又属于“类别”表中的一行。 现在,我在每个表上都设置了触发器,以在插入或更新时更新时间戳(updatedAt):

CREATE TRIGGER [Category_InsertUpdateDelete] ON [Category]
    AFTER INSERT, UPDATE, DELETE
AS
BEGIN
    SET NOCOUNT ON;
    IF TRIGGER_NESTLEVEL() > 3 RETURN;

    UPDATE [Category] SET [Category].[updatedAt] = CONVERT (DATETIMEOFFSET(3), SYSUTCDATETIME())
    FROM INSERTED
    WHERE INSERTED.id = [Category].[id]
END

现在我正在尝试更新父行的时间戳:

CREATE TRIGGER [Item_InsertUpdateDelete] ON [Item]
    AFTER INSERT, UPDATE, DELETE
AS
BEGIN
    SET NOCOUNT ON;
    IF TRIGGER_NESTLEVEL() > 3 RETURN;

    DECLARE @updatedAt DATETIMEOFFSET(3) = CONVERT(DATETIMEOFFSET(3), SYSUTCDATETIME());

    UPDATE [Item] SET [Item].[updatedAt] = @updatedAt
    FROM INSERTED
    WHERE INSERTED.id = [Item].[id]

    UPDATE [Category] SET [Category].[updatedAt] = @updatedAt
    FROM INSERTED
    WHERE INSERTED.categoryId = [Category].[id] AND [Category].[updatedAt] < @updatedAt;
END

不过有两个问题:
1)它导致死锁,因为项目触发器似乎正在等待类别触发器,两者都想更新类别。
2) 类别 updatedAt 时间戳将与项目时间戳不同,因为类别的触发器将再次更改它(产生毫秒左右的差异)。

我虽然使用 UPDATE() 函数检查 Category 触发器中的 updatedAt 列是否已更改,但我不清楚这是否适用于批量插入/更新。是否会检查 TRIGGER_NESTLEVEL 以查找可能导致这种“级联”的特定触发器,如果​​它返回超过 0 个则简单地返回?

执行这种时间戳“级联”的最佳方式是什么?

提前致谢!

【问题讨论】:

    标签: sql-server tsql triggers azure-sql-database


    【解决方案1】:

    如果您实际上没有尝试更新链上的相同时间戳字段,系统可能会更简单。也就是说,如果您确实需要顶部提供的所有信息,您可以通过拥有 Category.updateAt、Category.updateAtItem 和 Category.updateAtElement 来单独跟踪。您可以为任何级别的最新更新添加一个计算的(可能是持久的)列。

    或者,您可以引用加入关卡并提供“正确”updateAt 的视图。如果您不太频繁地需要该信息,这可能会起作用。

    但是,如果这些更改失败,请尝试检查 updateAt 字段是否不是正在更新的字段。所以在 Category_InsertUpdateDelete 中使用 if NOT UPDATE(updateAt) 来决定当它是级联触发器时退出。

    我也怀疑你正在处理 DELETE。您可能希望将 DELETE 逻辑分离到单独的触发器中。

    编辑:

    这是一个简化的视图示例,我可能会在提交级联触发器之前尝试查看大小:

    CREATE VIEW ParentChildUpdateAt AS
       SELECT id
             ,CASE WHEN updatedAt >= updatedAt_Child THEN updatedAt
                   ELSE updatedAt_Child
               END updatedAt
         FROM Parent 
              CROSS APPLY (
                 SELECT MAX(updatedAt) updatedAt_Child 
                   FROM Child 
                  WHERE Parent.Id = Child.Parent_id) Child
    GO
    

    这是一个如何使用 UPDATE() 函数来避免问题的示例。请注意,使用默认值可以简化插入时的触发器。

    CREATE TABLE Parent(
         id INT 
        ,data INT 
        ,updatedAt DATETIMEOFFSET(3) NOT NULL DEFAULT CONVERT (DATETIMEOFFSET(3), SYSUTCDATETIME())
    ) 
    GO
    CREATE TABLE Child(
         id INT 
        ,Parent_id INT 
        ,data INT 
        ,updatedAt DATETIMEOFFSET(3) NOT NULL DEFAULT CONVERT (DATETIMEOFFSET(3), SYSUTCDATETIME())
    )
    GO
    

    创建触发器 Parent_Update 父母 更新后 作为 如果不更新(更新时间) 更新父 SET updatedAt = CONVERT (DATETIMEOFFSET(3), SYSUTCDATETIME()) 从插入 WHERE Parent.id = Inserted.id 去吧

    CREATE TRIGGER Child_Insert 
                ON Child 
             AFTER INSERT
    AS BEGIN
    
      UPDATE Parent
         SET updatedAt = INSERTED.updatedAt
        FROM INSERTED 
       WHERE Parent.id = INSERTED.Parent_id
         AND INSERTED.updatedAt > Parent.updatedAt
    
    END
    GO
    
    CREATE TRIGGER Child_Update 
                ON Child 
             AFTER UPDATE
    AS BEGIN
    
      DECLARE @dt DATETIMEOFFSET(3) = CONVERT (DATETIMEOFFSET(3), SYSUTCDATETIME())
    
      IF UPDATE(updatedAt) 
        SELECT @dt = updatedAt 
          FROM INSERTED
      ELSE BEGIN
        SET @dt  = CONVERT (DATETIMEOFFSET(3), SYSUTCDATETIME())
        UPDATE Child
           SET updatedAt = @dt
          FROM INSERTED 
         WHERE Child.id = Inserted.id
      END
    
      UPDATE Parent
         SET updatedAt = @dt
        FROM INSERTED 
       WHERE Parent.id = INSERTED.Parent_id
         AND @dt > Parent.updatedAt
    
    END
    GO
    

    您可以使用here 讨论的方法重新组合子插入和更新

    触发器 Child_Delete 只需更新父日期。 Parent_Delete 的触发器是不必要的。

    【讨论】:

    • 好吧,其实没有必要拥有那么多信息。只需要知道该行或其任何后代的最后更新时间。我想视图不是一种选择。它被使用了很多。 UPDATE(updatedAt) 函数如何在批量更新中工作? (我不确定)。在我看来,这将是最好的选择。把它们分开有什么好处? (我对触发器不是很有经验)显然这段代码对 DELETE 没有用。
    • 非常感谢!例子很棒。
    猜你喜欢
    • 2014-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-25
    • 2012-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多