SQL Server - 从叶子到根的递归CTE(反向)

时间:2017-01-27 12:05:04

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

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

                   (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,如何从初始表中获取最终表?我需要将它从导致爆炸的根传播到根父。

1 个答案:

答案 0 :(得分:4)

这是一种方法:

首先,创建并填充样本表(在将来的问题中保存此步骤):

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

最后,测试更新是否正常:

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