【问题标题】:Dynamic T-SQL query for hierarchical data分层数据的动态 T-SQL 查询
【发布时间】:2020-10-16 06:59:57
【问题描述】:

我正在尝试进行动态查询以通过子-父母表,并且我已经能够通过分层查询的顶层和第二级:

数据:

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

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

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 来计算层次查询中的级别。根查询可以返回1 作为级别,递归调用可以增加它。我很确定文档示例显示了这一点
  • 能否详细说明您的专栏 j ?它代表什么?你应该怎么计算呢?
  • @JoPapou13 变量 j 背后的想法是获取层次结构中的总列数,从顶级父级 (j = 5) 开始,用于 while 条件和联合选择

标签: sql-server tsql dynamic-sql hierarchical-data recursive-query


【解决方案1】:

我根本看不到 for 动态 SQL。您有分层数据,您希望深度优先。在 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:

位置 |儿童 |家长 |测试 |等级 |小路 --: | :---- | :----- | ---: | --: | :------ 1 |一个 | 空 | 1 | 1 |一种 1 |乙|一个 | 2 | 2 |甲/乙 1 | C |乙| 3 | 3 | A/B/C 2 | D |乙| 4 | 3 | A/B/D 2 | E |一个 | 5 | 2 |空调 1 | F | E | 6 | 3 | A/E/F 2 |克| E | 7 | 3 | A/E/G 1 | H |克| 8 | 4 | A/E/G/H 2 |我 |克| 9 | 4 | A/E/G/I 2 | Ĵ | 空 | 10 | 1 | Ĵ 1 | ķ | Ĵ | 11 | 2 | J/K 2 |大号 | Ĵ | 12 | 2 |焦/升 3 |中号 | Ĵ | 13 | 2 |日/月 1 | N |中号 | 14 | 3 |日/月/日 2 | ○ |中号 | 15 | 3 | J/M/O 3 |磷 | 空 | 16 | 1 |磷 1 |问 |磷 | 17 | 2 | P/Q 1 |右 |问 | 18 | 3 | P/Q/R 2 |小号 |磷 | 19 | 2 |附言 3 | T |磷 | 20 | 2 |电汇 1 |你 | T | 21 | 3 | P/T/U 2 |五 | T | 22 | 3 | P/T/V 3 | W | T | 23 | 3 | P/T/W 4 | X |磷 | 24 | 2 | P/X 4 |是 | 空 | 25 | 1 |是 5 | Z | 空 | 26 | 1 | Z

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

【讨论】:

    【解决方案2】:

    您可以通过搜索有关递归查询的材料(如articles 或更早的answers)找到有关您所问内容的材料。

    为了创建您的递归查询,您创建一个CTE,其中第一个表是您的锚点,就像您的第一级有列Parent 是NULL。在同一个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
    

    【讨论】:

      【解决方案3】:

      似乎与其他同行发布的相同答案(我想到的..)具有出色的演示。但是,以下示例和 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!
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-24
        相关资源
        最近更新 更多