递归顺序(SQL Server CTE)

时间:2012-04-10 19:18:51

标签: sql-server recursion common-table-expression with-statement

我可以使用SQL Server的With命令(CTE)来实现递归。

WITH MyCTE(ParentID,ID,Name,Level)
AS
(
SELECT ManagerID AS ParentID, UserID AS ID, UserName AS Name, 0 AS Level 
FROM USERS U       
WHERE U.ManagerID IS NULL

UNION ALL

SELECT U.ManagerID AS ParentID, U.UserID AS ID, U.UserName AS Name, H.Level+1 AS Level
FROM USERS U
INNER JOIN MyCTE H ON H.ID = U.ManagerID
)

SELECT ParentID,ID FROM MyCTE

返回

ParentID    ID
NULL        1
1           2
1           3
2           4

我想要实现的是反转此结果集。即,将根节点和最深子节点反转为

ParentID    ID
NULL        4
4           2
2           1
3           1

无法弄清楚如何以编程方式实现它(最好通过使用CTE),例如使用参数来确定递归顺序等。非常感谢任何帮助,谢谢。

编辑:

修改了这一点,将我的第一个CTE结果插入到临时表中,然后使用另一个递归我反转顺序为(我知道“WHERE T.ID =(SELECT MAX(ID)FROM @tmp)”不会在实际情况,我还要用“Level”列确定最深的节点,只是为了这个例子试图简化这个),

 INSERT INTO @tmp
 SELECT ParentID,ID,Level FROM MyCTE
 WITH MyCTE2(ParentID,ID,Level)
 AS
 (
 SELECT NULL AS ParentID, ID AS ID, 0 AS Level FROM @tmp T 
 WHERE T.ID = (SELECT MAX(ID) FROM @tmp)

 UNION ALL

 SELECT R2.ID AS ParentID, T.ParentID AS ID, R2.Level+1 FROM @tmp T
 INNER JOIN MyCTE2 R2 ON R2.ID = T.ID
 WHERE T.ParentID IS NOT NULL
 )

原始结果(删除了1,3对)

ParentID   ID   Level
  NULL      1     0
    1       2     1
    2       4     2

反转结果,

ParentID   ID   Level
  NULL      4     0
    4       2     1
    2       1     2

编辑2:

我做了类似的事,

SELECT TTT.ParentID,TTT.ID,TTT.Level FROM
(
SELECT ParentID,ID,Level FROM MyCTE2
UNION ALL
SELECT TT.ID AS ParentID,TT.ParentID AS ID,(SELECT Level+1 FROM @tmp WHERE ID=TT.ID) 
AS  Level  FROM
(
SELECT ID FROM @tmp
EXCEPT
SELECT ID FROM MyCTE2
)T INNER JOIN @tmp TT ON TT.ID = T.ID
)TTT
ORDER BY TTT.Level

给出,

ParentID    ID  Level
NULL        4   0
4           2   1
2           1   2
3           1   2

这可能包含错误,我还不确定,只是想显示以确保对(3,1)与2级是否正确?我现在已经考虑了很长一段时间,我可能会犯一些愚蠢的错误。

1 个答案:

答案 0 :(得分:4)

示例数据

declare @T table
(
  ParentID int,
  ID int
)

insert into @T values
(NULL,        1),
(1   ,        2),
(1   ,        3),
(2   ,        4)

来自root的递归:

;with C as
(
  select ParentID, ID
  from @T
  where ParentID is null
  union all
  select T.ParentID, T.ID
  from @T as T
    inner join C
      on T.ParentID = C.ID
)
select *
from C

结果

ParentID    ID
----------- -----------
NULL        1
1           2
1           3
2           4

来自叶子的递归:

;with C as
(
  select null as PParentID, ID, ParentID
  from @T
  where ID not in (select ParentID 
                   from @T 
                   where ParentID is not null)
  union all
  select C.ID, T.ID, T.ParentID 
  from @T as T
    inner join C
      on T.ID = C.ParentID 
)
select distinct
       PParentID as ParentID,
       ID
from C

结果:

ParentID    ID
----------- -----------
NULL        3
NULL        4
4           2
2           1
3           1

如果你有很多分支,你将有重复的行作为合并在一起。使用distinct可以解决这个问题。

要获得正确的水平,您需要先从上到下计算水平。将其存储在表变量(或临时表)中,然后将其用作leaf-> root递归的源。

-- Primary key and unique is in there to get the indexes used in the recursion  
declare @T2 table
(
  ParentID int,
  ID int,
  Level int,
  primary key (ID),
  unique(ParentID, ID)
)

;with C as
(
  select ParentID, ID, 0 as Level
  from @T
  where ParentID is null
  union all
  select T.ParentID, T.ID, Level + 1
  from @T as T
    inner join C
      on T.ParentID = C.ID
)
insert into @T2
select ParentID, ID, Level
from C

;with C as
(
  select null as PParentID, ID, ParentID, Level
  from @T2
  where ID not in (select ParentID 
                   from @T2 
                   where ParentID is not null)
  union all
  select C.ID, T.ID, T.ParentID, T.Level
  from @T2 as T
    inner join C
      on T.ID = C.ParentID 
)
select distinct
       PParentID as ParentID,
       ID,
       max(Level) over() - Level as level
from C

结果:

ParentID    ID          level
----------- ----------- -----------
NULL        3           1
NULL        4           0
2           1           2
3           1           2
4           2           1

使用多CTE查询替换@T2是可能的,但这是一个非常糟糕的主意。它会破坏性能,因为每次递归都会重建第一个CTE。至少那是我对正在发生的事情的猜测,但相信我并不快。