2014-03-01 39 views
0

我有三个表,“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,则只是返回?

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

在此先感谢!

回答

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 ON父 UPDATE AS 之后,如果不更新(updatedAt) 更新父 SET updatedAt = CONVERT(DATETIMEOFFSET(3),SYSUTCDATETIME()) FROM INSERTED WHERE父。 id =插入。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的触发器是不必要的。

+0

嗯,这并不是真的有必要有这么多的信息。只需要知道该行或其子代的最后更新时间。 我猜一个视图不是一个选项;它用了很多。 UPDATE(updatedAt)函数如何在批量更新中工作? (我对此不确定)。在我看来,这将是最好的选择。 将它们分开的优点是什么? (我对触发器不熟悉)显然这段代码对DELETE没有用处。 – Alexander

+0

@亚历山大:我已经用例子更新了我的答案。 –

+0

非常感谢!这些例子很棒。 – Alexander