【问题标题】:SQL Server - Recursive CTE from leafs to root (inverse)SQL Server - 从叶子到根的递归 CTE(逆)
【发布时间】:2017-01-27 12:05:03
【问题描述】:

想象以下场景: 我有很多级别,从上级(根父级)到下级(子级或叶子)。

                   (root parent)               LEVEL 0
                       ID:98
                      /     \
                     /       \
                    /         \
                   o           +               LEVEL 1
                ID:99        ID:100
                             /   \
                            /     \
                           o       +           LEVEL 2
                        ID:101   ID:102
                                  / \
                                 /   \ 
                                o     o        LEVEL 3         
                            ID:201  ID:202

现在想象一下,“+”符号是房间。同层的房间之间无法通信。每个房间都有一些大门。通过这些门,您可以与下一层的其他房间(孩子)交流。

符号“o”是叶子,我的意思是,房间没有门可以进入较低级别的其他房间。

这里为了简单起见,每个房间都有两个大门,但也可以有两个以上。

所以现在,最后想象一下:如果爆炸起源于属于父房间的任何子房间/叶子房间,那么父房间的所有门都将自动关闭,以防止爆炸传播到根父房间。

想象一下下表:

ROOM_ID | PARENT_ROOM | GATES_OPEN | EXPLOSION
    98       NULL          1         0
    99        98           1         0
   100        98           1         0
   102       100           1         0
   101       100           1         0 
   200       102           -         0
   201       102           -         0

所有房间的所有大门都打开,因为最初没有爆炸。 房间 200 和 201 没有大门。

假设每个房间都有一个传感器来检测可能发生的爆炸。如果传感器检测到爆炸,则信号会传播到父房间,父房间会关闭所有大门。该信号也向上传播到父房间,所有父房间也关闭其所有门,依此类推,直到到达根父房间,它也关闭所有门。

所以现在假设房间 ID:102 发生爆炸,所以我需要更新下表:

ROOM_ID | PARENT_ROOM | GATES_OPEN | EXPLOSION
    98       NULL          0         0
    99        98           1         0
   100        98           0         0
   102       100           1         1
   101       100           1         0 
   200       102           -         0
   201       102           -         0

那么使用递归CTE,如何从初始表中获取更新后的最终表呢?我需要将它从引起爆炸的根传播到根父级。

【问题讨论】:

    标签: sql-server sql-server-2008 recursion sql-server-2008-r2 common-table-expression


    【解决方案1】:

    这是一种方法:

    首先,创建并填充示例表(在您以后的问题中保存我们这一步):

    DECLARE @T AS TABLE
    (
        ROOM_ID int,
        PARENT_ROOM int,
        GATES_OPEN bit,
        EXPLOSION bit
    )
    
    
    INSERT INTO @T VALUES
    (98, NULL, 1, 0),
    (99, 98, 1, 0),
    (100, 98, 1, 0),
    (102, 100, 1, 0),
    (101, 100, 1, 0),
    (200, 102, NULL, 0),
    (201, 102, NULL, 0)
    

    然后,创建 CTE:

    DECLARE @RoomId int = 102;
    
    ;WITH CTE AS
    (
        SELECT ROOM_ID
              ,PARENT_ROOM
              ,GATES_OPEN
              ,CAST(1 AS BIT) AS EXPLOSION
        FROM @T 
        WHERE ROOM_ID = @RoomId 
        UNION ALL
        SELECT t.ROOM_ID
              ,t.PARENT_ROOM
              ,CAST(0 AS BIT) AS GATES_OPEN
              ,t.EXPLOSION
        FROM @T t 
        INNER JOIN CTE ON t.ROOM_ID = CTE.PARENT_ROOM
    )
    

    更新表格:

    UPDATE t 
    SET GATES_OPEN = CTE.GATES_OPEN,
        EXPLOSION = CTE.EXPLOSION
    FROM @T t
    INNER JOIN CTE ON t.ROOM_ID = CTE.ROOM_Id
    

    最后,测试更新是否OK:

    SELECT *
    FROM @T 
    

    结果:

    ROOM_ID PARENT_ROOM GATES_OPEN  EXPLOSION
    98      NULL        0           0
    99      98          1           0
    100     98          0           0
    102     100         1           1
    101     100         1           0
    200     102         NULL        0
    201     102         NULL        0
    

    更新

    如果你不知道爆炸发生在哪个房间(我猜是某个进程更新了数据库表并将爆炸值设置为1),那么你可以在表上使用触发器。和我之前写的查询差不多,结果也一样:

    CREATE TRIGGER tr_Rooms_Update ON Rooms 
    FOR UPDATE
    AS
    
    ;WITH CTE AS
    (
        SELECT ROOM_ID
              ,PARENT_ROOM
              ,GATES_OPEN
              ,EXPLOSION
        FROM inserted 
        WHERE EXPLOSION = 1
        UNION ALL
        SELECT t.ROOM_ID
              ,t.PARENT_ROOM
              ,CAST(0 AS BIT) AS GATES_OPEN
              ,t.EXPLOSION
        FROM Rooms t 
        INNER JOIN CTE ON t.ROOM_ID = CTE.PARENT_ROOM
    )
    
    UPDATE t 
    SET GATES_OPEN = CTE.GATES_OPEN,
        EXPLOSION = CTE.EXPLOSION
    FROM Rooms t
    INNER JOIN CTE ON t.ROOM_ID = CTE.ROOM_Id
    
    GO
    

    【讨论】:

    • 发生爆炸的房间没有关门。只有父房间可以。您需要添加一个过滤器以将该房间从您的更新中排除,以便您的输出与问题中的预期输出相匹配。
    • @iamdave:很好!我已经更改了更新声明,谢谢。
    • @ZoharPeled 是的,当指示房间发生混凝土爆炸时,此解决方案有效,但想象一下我不知道哪个房间,从树叶到根部,我需要检查每个房间的爆炸值,如果它的任何孩子都将爆炸值设置为 1,然后将父 gates_open 值设置为 0,依此类推,直到到达根。
    • 我明白了。这在问题中不是很清楚。在这种情况下,我会在使用计算机时编辑我的答案。
    • @ZoharPeled 考虑到一个房间可以有 1、2 或超过 2 个孩子(房间),如果是叶子,则为 0。
    猜你喜欢
    • 2014-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-23
    • 1970-01-01
    • 1970-01-01
    • 2012-12-25
    • 1970-01-01
    相关资源
    最近更新 更多