创建一个将层次结构级别放入列中的查询

问题描述 投票:0回答:1

我正在尝试在 mssql 中编写一个查询,将下面的输入表转换为所需的输出

我有一个表,通过使用

parent_id
列的自引用来表示层次结构。

这是涵盖我的案例的输入表示例。

id current_level 姓名 parent_id
1 1 老A
2 1 最老的B
3 2 老A 1
4 2 老B 2
5 3 小A 3
6 3 小B 4
7 3 小C 4
8 4 老幺A 5
9 4 最小的B 6
10 4 最小的C 7
11 4 老幺D 7
12 4 最小的E 7
13 4 老幺F 7

这里的想法是通过添加与级别一样多的列来扩展此表。在这种情况下,我们有 4 个级别,所以我们想向表中添加 4 个新列。

这是期望的输出

id current_level 姓名 parent_id level_1 level_2 level_3 level_4
1 1 老A 老A
2 1 最老的B 最老的B
3 2 老A 1 老A 老A
4 2 老B 2 最老的B 老B
5 3 小A 3 老A 老A 小A
6 3 小B 4 最老的B 老B 小B
7 3 小C 4 最老的B 老B 小C
8 4 老幺A 5 老A 老A 小A 老幺A
9 4 最小的B 6 最老的B 老B 小B 最小的B
10 4 最小的C 7 最老的B 老B 小C 最小的C
11 4 老幺D 7 最老的B 老B 小C 老幺D
12 4 最小的E 7 最老的B 老B 小C 最小的E
13 4 老幺F 7 最老的B 老B 小C 老幺F

我在这里面临的两个主要挑战:

  1. 级别数可以是动态的,比方说 1-30(在这个例子中,我只展示了 4 个级别,但输入数据可能会有更多级别,例如 25,所以最好使这个查询动态任意数量的级别,而不仅仅是 4)
  2. 在级别 3 和更高级别,我们需要查找的不仅仅是直接父元素 - 基本上我们需要遍历整个查找链,直到找到根元素 (
    parent_id == null
    )。

例如,要使用

id = 5
填充行的级别,我正在使用这样的逻辑:

  • if column_level > current_level then SET to NULL
    => 列 level_4 = NULL
  • if column_level == current_level then SET to current_level
    => column level_3 = Young A
  • if column_level == current_level-1 then SET to parent_level
    => 列 level_2 = 旧 A
  • if column_level == current_level-2 then SET to parent_of_parent_level
    => 列 level_1 = 最旧的 A
  • 我们到达了根级别,因为我们在 level_1 上并且 parent_of_parent_of_parent 不存在 => 第 5 行完成了

id=5 行的查找链

3(parent)->1(parent_of_parent)->NULL


我尝试了什么:

我试图用递归 cte 来解决这个问题,但是那些不允许递归地附加新列或者至少我不能。

我也开始考虑用 TSQL 解决方案,但也没有运气。

sql sql-server recursion common-table-expression hierarchy
1个回答
0
投票

您可以为此使用递归 CTE。 要获得父级别,您只需在递归部分将它们连接在一起,使用 JSON 或 XML,以便您可以将其解析为列表。

由于您希望将它们全部作为单独的列,因此变得更加困难。因此,您需要在

PIVOT
内使用一个巨大的
APPLY
来获得所有三十列(我没有费心重命名它们,它们都只是
1
2
3
等)。

WITH cte AS (
    SELECT
      t.id,
      t.current_level,
      t.name,
      t.parent_id,
      levels = STRING_ESCAPE(t.name, 'json')
    FROM YourTable t
    WHERE current_level = 1

    UNION ALL

    SELECT
      t.id,
      t.current_level,
      t.name,
      t.parent_id,
      cte.levels + '","' + STRING_ESCAPE(t.name, 'json')
    FROM cte
    JOIN YourTable t ON t.parent_id = cte.id
)
SELECT
  cte.id,
  cte.current_level,
  cte.name,
  cte.parent_id,
  levels.*
FROM cte
CROSS APPLY (
    SELECT *
    FROM (
        SELECT
          [key] = CAST([key] AS int) + 1,
          value
        FROM OPENJSON('["' + cte.levels + '"]')
    ) o
    PIVOT (
        MAX(value) FOR [key] IN (
          [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22], [23], [24], [25], [26], [27], [28], [29], [30]
        )
    ) p
) levels;

如果你不介意只得到一个大的 JSON blob,你可以删除

APPLY
PIVOT

WITH cte AS (
    SELECT
      t.id,
      t.current_level,
      t.name,
      t.parent_id,
      levels = STRING_ESCAPE(t.name, 'json')
    FROM YourTable t
    WHERE current_level = 1

    UNION ALL

    SELECT
      t.id,
      t.current_level,
      t.name,
      t.parent_id,
      cte.levels + '","' + STRING_ESCAPE(t.name, 'json')
    FROM cte
    JOIN YourTable t ON t.parent_id = cte.id
)
SELECT
  cte.id,
  cte.current_level,
  cte.name,
  cte.parent_id,
  levels = '["' + cte.levels + '"]'
FROM cte;

db<>小提琴

© www.soinside.com 2019 - 2024. All rights reserved.