【问题标题】:How am I misunderstanding trigger recursion in TSQL?我如何误解 SQL 中的触发器递归?
【发布时间】:2010-01-12 10:29:39
【问题描述】:

我有下表:

if object_id(N'dbo.Node') is null
create table dbo.Node
(
    ID bigint identity primary key,
    ParentID bigint null, -- references Node(ID)
    DateCreated datetime not null,
    LastUpdated datetime not null,
    [Name] nvarchar(500) not null,
);

我有这个触发器来实现同一个表中的级联删除:

create trigger Node_Delete on Node for delete
as
begin
    delete from Node where ParentID in (select id from deleted)
end

这是我的数据集:

ID                   ParentID             DateCreated             LastUpdated             Name
534                  514                  2010-01-12 10:15:03.940 2010-01-12 10:15:03.940 Test 1
535                  534                  2010-01-12 10:15:08.563 2010-01-12 10:15:08.563 Test 2
536                  535                  2010-01-12 10:15:12.063 2010-01-12 10:15:12.063 Test 3
537                  536                  2010-01-12 10:15:18.510 2010-01-12 10:15:18.510 Test 4

现在我执行这个查询:

delete from Node where ID=534

这是生成的数据集:

ID                   ParentID             DateCreated             LastUpdated             Name
536                  535                  2010-01-12 10:15:12.063 2010-01-12 10:15:12.063 Test 3
537                  536                  2010-01-12 10:15:18.510 2010-01-12 10:15:18.510 Test 4

为什么触发器中的 DELETE 语句直到所有下降的记录都被删除后才会递归执行触发器?

编辑:请注意,我在下面发布了一个可行的解决方案,但将另一个答案标记为正确,因为我的问题不是“解决方案是什么”,而是“为什么我这样做的方式不起作用”。

【问题讨论】:

    标签: sql-server tsql triggers


    【解决方案1】:

    我修改了触发器以级联到无限深度,而没有打开触发器递归。这是我的解决方案:

    create trigger Node_Delete on Node instead of delete
    as
    begin
        create table #del ( id bigint, depth int )
        declare @depth int
        set @depth = 1
        insert into #del select id, @depth from deleted
        while @@rowcount > 0
        begin
            set @depth = @depth + 1
            insert into #del select id, @depth from Node where ParentID in (select id from #del where depth = @depth-1)
        end
        delete from Node where ID in (select id from #del)
    end
    

    编辑:我现在已经确定了使用公用表表达式的更好解决方案,正如 Mladen Prajdic 在下面的另一个答案中所建议的那样。

    create trigger Node_Delete on Node instead of delete
    as
    begin
        with nodes
        as
        (
            select n.ID, n.ParentID, 1 as Level
            from Node n where n.ID in (select ID from deleted)
            union all
            select n.ID, n.ParentID, p.Level+1 as Level
            from Node n
            inner join nodes p on p.ID = n.ParentID
        )
        delete from Node where ID in (select ID from nodes);
    end
    

    【讨论】:

      【解决方案2】:

      您必须为数据库启用递归触发器。请注意,您只能更深 32 级。

      ALTER DATABASE databasename
      SET RECURSIVE_TRIGGERS ON | OFF
      

      【讨论】:

        【解决方案3】:

        打开嵌套触发器(或将 Allow Triggers to Fire Others 选项设置为 True,具体取决于您的 sql 版本)

        sql server properties screenshot http://img34.imageshack.us/img34/5259/capturehjf.png

        【讨论】:

          【解决方案4】:

          因为删除的伪表只有一行 id 为 534。

          您可以在父子关系上添加级联删除
          或使用 CTE 删除所有内容。

          【讨论】:

          • 您不能在子-父关系上添加级联删除,因为 SQL Server 抱怨多个级联路径。请注意,我更新了我在上面提出问题的方式。我不明白为什么触发器不会因为删除而自行关闭。
          • 哦,对了。忘记了。那么你必须使用 CTE:msdn.microsoft.com/en-us/library/ms190766.aspx
          • 我可以查找 CTE,但我仍然想知道为什么我不能使用触发器来执行此操作...
          • 你也可以设置嵌套触发器选项:msdn.microsoft.com/en-us/library/ms178101.aspx 但要小心,因为它是服务器范围的设置,我不推荐它
          【解决方案5】:

          FOR DELETE 触发器在删除操作之后触发。因此,任何子记录都违反了 FK 约束,因此无法删除。

          您可以按照我的博客中的描述写一个INSTEAD OF DELETE trigger 来解决这个问题。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-03-28
            • 2010-11-08
            • 2012-01-12
            • 1970-01-01
            • 2016-06-10
            • 2015-01-08
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多