递归 CTE 将所有祖先列为列

Recursive CTE listing all ancestors as columns

我发现了许多显示递归 CTE 的示例,这些示例将路径列为串联字符串和关卡深度。例如:

CREATE TABLE #Test
    (ID varchar(20),
    ParentID varchar(20)
)

INSERT INTO #Test values ('100000','HEAD');
INSERT INTO #Test values ('100100','100000');
INSERT INTO #Test values ('100200','100100');
INSERT INTO #Test values ('100300','100200');
INSERT INTO #Test values ('100400','100300');


;WITH CTE AS
(
    SELECT ID, ParentID, cast(ID as varchar(100)) as path, 0 AS Level
    FROM #Test
    WHERE ParentID = 'HEAD' 
    UNION ALL
    SELECT t.Id,t.ParentID, cast(cte.path +'>'+ t.ID as varchar(100)),   Level + 1
    FROM #Test t
    INNER JOIN CTE ON t.ParentID = CTE.ID
)

SELECT * FROM CTE
DROP table #Test

有没有办法 return 每行中的每个祖先都在单独的列中?因此,而不是当前结果:

ID      ParentId Path                               Level
100000  HEAD     100000                             0
100100  100000   100000>100100                      1
100200  100100   100000>100100>100200               2
100300  100200   100000>100100>100200>100300        3
100400  100300   100000>100100>100200>100300>100400 4

它将是:

Id      Parent  L0        L1     L2     L3     L4
100000  HEAD    100000    Null   Null   Null   Null
100100  100000  100000    100100 Null   Null   Null
100200  100100  100000    100100 100200 Null   Null
100300  100200  100000    100100 100200 100300 Null 
100400  100300  100000    100100 100200 100300 100400   

假设我知道深度的最大级别,在此示例中,最大级别为 5。SQL 服务器版本。 12.0,数据库兼容性为 120。

如果您有已知或最大级别数,请考虑 JSON。

如果不是 2016+ ...有一个类似的 XML 方法。

例子

;WITH CTE AS
(
    SELECT ID, ParentID, cast(ID as varchar(100)) as path, 0 AS Level
    FROM #Test
    WHERE ParentID = 'HEAD' 
    UNION ALL
    SELECT t.Id,t.ParentID, cast(cte.path +'>'+ t.ID as varchar(100)),   Level + 1
    FROM #Test t
    INNER JOIN CTE ON t.ParentID = CTE.ID
)
SELECT A.ID
      ,A.ParentID
      ,B.*
      ,A.Level
  FROM CTE A
  Cross Apply (
                Select L0 = trim(JSON_VALUE(S,'$[0]'))
                      ,L1 = trim(JSON_VALUE(S,'$[1]'))
                      ,L2 = trim(JSON_VALUE(S,'$[2]'))
                      ,L3 = trim(JSON_VALUE(S,'$[3]'))
                      ,L4 = trim(JSON_VALUE(S,'$[4]'))
                      ,L5 = trim(JSON_VALUE(S,'$[5]'))
                 From  ( values ( '["'+replace(path,'>','","')+'"]' ) ) A(S)
              ) B

Returns

更新:XML 方法

;WITH CTE AS
(
    SELECT ID, ParentID, cast(ID as varchar(100)) as path, 0 AS Level
    FROM #Test
    WHERE ParentID = 'HEAD' 
    UNION ALL
    SELECT t.Id,t.ParentID, cast(cte.path +'>'+ t.ID as varchar(100)),   Level + 1
    FROM #Test t
    INNER JOIN CTE ON t.ParentID = CTE.ID
)
SELECT A.ID
      ,A.ParentID
      ,B.*
      ,A.Level
  FROM CTE A
  Cross Apply (
               Select  L0 = xDim.value('/x[1]','varchar(50)')
                      ,L1 = xDim.value('/x[2]','varchar(50)')
                      ,L2 = xDim.value('/x[3]','varchar(50)')
                      ,L3 = xDim.value('/x[4]','varchar(50)')
                      ,L4 = xDim.value('/x[5]','varchar(50)')
                      ,L5 = xDim.value('/x[6]','varchar(50)')
                From  (Select Cast('<x>' + replace(Path,'>','</x><x>')+'</x>' as xml) as xDim) as A 
              ) B

正如大家提到的,你需要知道你拥有的关卡数量,但这里是 PIVOT:

的实现
;WITH CTE AS
(
    SELECT ID, ParentID, cast(ID as varchar(100)) as path, 0 AS Level
    FROM #Test
    WHERE ParentID = 'HEAD' 
    UNION ALL
    SELECT t.Id,t.ParentID, cast(cte.path +'>'+ t.ID as varchar(100)),   Level + 1
    FROM #Test t
    INNER JOIN CTE ON t.ParentID = CTE.ID
)
select 
*
from (
    Select Id , ParentID,
    'L'+ CAST(Row_Number() Over (Partition By ID Order By ID) AS VARCHAR)AS Col,
    Split.value 
    , level
    From CTE
    Cross apply string_split(path,'>') as Split
) AS tbl
Pivot (Max(value) For Col IN ([L1],[L2],[L3],[L4],[L5])) AS Pvt