分层数据的动态 T-SQL 查询

Dynamic T-SQL query for hierarchical data

我正在尝试进行动态查询以通过 child - parents table 并且我已经能够通过分层查询的顶级和二级:

数据:

create table temp 
(
     Pos int
    ,Child nvarchar(18)
    ,Parent nvarchar(18)
    ,Test int
);

insert into temp (Pos, Child, Parent, Test)
values
(1, 'A', NULL, 1),
(2, 'J', NULL, 10),
(3, 'P', NULL, 16),
(4, 'Y', NULL, 25),
(1, 'B', 'A', 2),
(2, 'E', 'A', 5),
(1, 'C', 'B', 3),
(2, 'D', 'B', 4),
(1, 'F', 'E', 6),
(2, 'G', 'E', 7),
(1, 'H', 'G', 8),
(2, 'I', 'G', 9),
(1, 'K', 'J', 11),
(2, 'L', 'J', 12),
(3, 'M', 'J', 13),
(1, 'N', 'M', 14),
(2, 'O', 'M', 15),
(5, 'Z', NULL, 26),
(1, 'Q', 'P', 17),
(2, 'S', 'P', 19),
(3, 'T', 'P', 20),
(4, 'X', 'P', 24),
(1, 'R', 'Q', 18),
(1, 'U', 'T', 21),
(2, 'V', 'T', 22),
(3, 'W', 'T', 23)

Test列只看末尾数据排序是否正确

到目前为止我的代码:

declare @sql nvarchar(max);
declare @tlp nvarchar(max); --top level parents
declare @i nvarchar(4);
declare @j nvarchar(4);
declare @l nvarchar(4); --level

set @tlp = ';with tlp as (
       select ROW_NUMBER() over (order by Pos) as j, * from temp where Parent IS NULL
       )';
set @i = 1;
set @j = (select COUNT(*) as j from temp where Parent IS NULL);
set @sql = @tlp;

while @i < @j
    begin
        set @l = 1;
        set @sql += '
            select ' + @l + ' as Level, * from tlp where j = ' + @i
        
        set @l = @l + 1
        set @sql += '
            union all
            select ' + @l + ' as Level, ROW_NUMBER() over (order by Pos), * from temp where Parent = (select Child from tlp where j = ' + @i + ')'
        set @i = @i + 1
        if @i < @j set @sql += '
            union all'
end;

exec(@sql);

输出:

level   j   Pos Child   Parent  Test
1       1   1   A       NULL    1
2       1   1   B       A       2
2       2   2   E       A       5
1       2   2   J       NULL    10
2       1   1   K       J       11
2       2   2   L       J       12
2       3   3   M       J       13
1       3   3   P       NULL    16
2       1   1   Q       P       17
2       2   2   S       P       19
2       3   3   T       P       20
2       4   4   X       P       24
1       4   4   Y       NULL    25

如何扩展查询以动态遍历所有 child?这是所需的输出:

Level   j   Pos Child   Parent  Test
1       1   1   A       NULL    1
2       1   1   B       A       2
3       1   1   C       B       3
3       2   2   D       B       4
2       2   2   E       A       5
3       1   1   F       E       6
3       2   2   G       E       7
4       1   1   H       G       8
4       2   2   I       G       9
1       2   2   J       NULL    10
2       1   1   K       J       11
2       2   2   L       J       12
2       3   3   M       J       13
3       1   1   N       M       14
3       2   2   O       M       15
1       3   3   P       NULL    16
2       1   1   Q       P       17
3       1   1   R       Q       18
2       2   2   S       P       19
2       3   3   T       P       20
3       1   1   U       T       21
3       2   2   V       T       22
3       3   3   W       T       23
3       4   4   X       P       24
1       4   4   Y       NULL    25
1       5   5   Z       NULL    26

这是我试图实现的视觉解释:

我根本没有看到动态 SQL。您有分层数据,您希望以 depth-first 的形式遍历。在 SQL 中,这通常是通过递归查询完成的。要管理行的顺序,您可以跟踪每个节点的路径。

考虑:

with cte as (
    select t.*, 1 lvl, cast(child as nvarchar(max)) path 
    from temp t 
    where parent is null
    union all
    select t.*, c.lvl + 1, c.path + '/' + cast(t.child as nvarchar(max))
    from cte c
    inner join temp t on t.parent = c.child
)
select * from cte order by path

Demo on DB Fiddle:

Pos | Child | Parent | Test | lvl | path   
--: | :---- | :----- | ---: | --: | :------
  1 | A     | null   |    1 |   1 | A      
  1 | B     | A      |    2 |   2 | A/B    
  1 | C     | B      |    3 |   3 | A/B/C  
  2 | D     | B      |    4 |   3 | A/B/D  
  2 | E     | A      |    5 |   2 | A/E    
  1 | F     | E      |    6 |   3 | A/E/F  
  2 | G     | E      |    7 |   3 | A/E/G  
  1 | H     | G      |    8 |   4 | A/E/G/H
  2 | I     | G      |    9 |   4 | A/E/G/I
  2 | J     | null   |   10 |   1 | J      
  1 | K     | J      |   11 |   2 | J/K    
  2 | L     | J      |   12 |   2 | J/L    
  3 | M     | J      |   13 |   2 | J/M    
  1 | N     | M      |   14 |   3 | J/M/N  
  2 | O     | M      |   15 |   3 | J/M/O  
  3 | P     | null   |   16 |   1 | P      
  1 | Q     | P      |   17 |   2 | P/Q    
  1 | R     | Q      |   18 |   3 | P/Q/R  
  2 | S     | P      |   19 |   2 | P/S    
  3 | T     | P      |   20 |   2 | P/T    
  1 | U     | T      |   21 |   3 | P/T/U  
  2 | V     | T      |   22 |   3 | P/T/V  
  3 | W     | T      |   23 |   3 | P/T/W  
  4 | X     | P      |   24 |   2 | P/X    
  4 | Y     | null   |   25 |   1 | Y      
  5 | Z     | null   |   26 |   1 | Z      

如果一个路径可能有超过100个节点,那么你需要在查询的末尾添加option(maxrecursion 0),否则你将达到SQL服务器允许的最大递归级别默认。

您可以通过在 articles or older .

等递归查询中搜索 material 来找到 material 您要询问的内容

为了创建递归查询,您创建了一个 CTE,其中第一个 table 是您的锚点,就像您的第一个级别,其中列 ParentNULL。在同一个 CTE 中,您不断将级别加 1。请在Fiddle

中寻找答案
WITH MyCTE AS (
SELECT *, 1 AS Level
FROM temp
WHERE Parent IS NULL

UNION ALL

SELECT t.Pos, t.Child, t.Parent, t.Test, MyCTE.Level+1 AS Level
FROM temp AS t
INNER JOIN MyCTE
ON t.Parent = MyCTE.Child
WHERE t.Parent IS NOT NULL)
SELECT MyCTE.*, CASE WHEN Offsprings.Offspring IS NULL THEN 1 ELSE Offsprings.Offspring END AS Offspring
FROM MyCTE
LEFT JOIN (
    SELECT Parent, COUNT(Parent) AS Offspring
    FROM temp
    GROUP BY Parent)Offsprings
ON MyCTE.Child = Offsprings.Parent
ORDER BY MyCTE.Child

其他同行发布的答案似乎相同(我想到的..),演示很棒。但是,以下示例和 this post 可能有助于简单和更好地理解递归 CTE

DDL


create table temp 
(
    recid int identity (1,1)
    ,Pos_ID int
    ,Child_Pos nvarchar(50)
    ,Parent_Pos nvarchar(50)
);

insert into temp (Pos_ID, Child_Pos, Parent_Pos)
values
(1, 'Super Boss', NULL),
(2, 'Boss', 'Super Boss'),
(3, 'Sr. Mangaer 1', 'Boss'),
(3, 'Sr. Mangaer 2', 'Boss'),
(3, 'Sr. Mangaer 3', 'Boss'),
(4, 'Mangaer 1', 'Sr. Mangaer 1'),
(4, 'Mangaer 2', 'Sr. Mangaer 1'),
(4, 'Mangaer 3', 'Sr. Mangaer 2'),
(4, 'Mangaer 4', 'Sr. Mangaer 2'),
(4, 'Mangaer 5', 'Sr. Mangaer 3'),
(4, 'Mangaer 6', 'Sr. Mangaer 3'),
(5, 'Emp 01', 'Mangaer 1'),
(5, 'Emp 02', 'Mangaer 1'),
(5, 'Emp 03', 'Mangaer 2'),
(5, 'Emp 04', 'Mangaer 2'),
(5, 'Emp 05', 'Mangaer 3'),
(5, 'Emp 06', 'Mangaer 3'),
(5, 'Emp 07', 'Mangaer 4'),
(5, 'Emp 08', 'Mangaer 4'),
(5, 'Emp 09', 'Mangaer 5'),
(5, 'Emp 10', 'Mangaer 5'),
(5, 'Emp 11', 'Mangaer 6'),
(5, 'Emp 12', 'Mangaer 6')
go

递归 CTE 示例

with main as (
select Child_Pos, Parent_Pos,Pos_ID, 1 as Reculevel
from temp as t1
--where Parent_Pos is not null 

UNION ALL

select t2.Child_Pos, t2.Parent_Pos, t2.Pos_ID, main.Reculevel + 1
from temp as t2 
join main on t2.Parent_Pos = main.Child_Pos
)

select * from main

遵循您的示例的递归 CTE

with main as (
select Pos, Child, Parent, Test, 1 as RecuLevel
from temp as t1

UNION ALL

select t2.Pos, t2.Child, t2.Parent, t2.Test, RecuLevel + 1
from temp as t2 
join main on t2.Parent = main.Child
)

select * from main
--option (maxrecursion 0) -- be cautious enabling this!