如何“级联”更新时间戳(使用触发器)

时间:2014-03-01 22:27:59

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

我有三个表,“elements”表中的行属于“items”表中的一行,而这些行又属于“categories”表中的一行。 现在,我已经在每个表上设置了触发器来更新插入或更新时的时间戳(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)更新的类别时间戳将与项目时间戳不同,因为类别的触发器将再次更改它(使得差异为毫秒左右)。

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

这个“级联”时间戳的最佳方法是什么?

提前致谢!

1 个答案:

答案 0 :(得分:0)

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

或者,您可以引用加入关卡并提供“正确”updateAt的视图。如果您不太需要这些信息,可能会这样做。

但是,如果更改失败,请尝试检查以查看updateAt字段不是要更新的字段。因此,在Category_InsertUpdateDelete中,如果不是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

CREATE TRIGGER Parent_Update                 父母              更新后     如       如果没有更新(updatedAt)         更新父母            SET updatedAt = CONVERT(DATETIMEOFFSET(3),SYSUTCDATETIME())           来自插入          WHERE Parent.id = Inserted.id     GO

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的触发器。