分层结构,新列,非规范化

时间:2017-04-11 10:18:49

标签: sql sql-server tsql common-table-expression denormalization

假设我有这样的表:

id  parent_id    name
11     NULL      Company
33      11       Department 1
44      33       Department 2

我想将其转换为:

id    parent_id    name         Level1      Level2       Level3
11      NULL       Company       NULL        NULL         NULL
22      11         Company   Department 1    NULL         NULL
33      22         Company   Department 1  Department 2   NULL

我能够创建一个CTE,并在Levels列中显示层次结构中的值,但我不知道如何为所显示的部门创建新列。

with myCTE as (
      select c.id, c."name", c.parent_id, 1 as Level
      from table1 c
      where c.parent_id IS NULL

      UNION ALL

      Select c1.id, c1."name", c1.parent_id, Level +1
      from table1 c1
      inner join myCTE on c1.parent_id = myCTE.id
      where c1.parent_id IS NOT NULL
      )

select * from myCTE

表示:

         id       parent_id    name         level
   1     11        NULL        Company        1
   2     22        11          Department 2   2
   3     33        22          Department 3   3

2 个答案:

答案 0 :(得分:2)

(几乎)完全通用的方法:

DECLARE @tbl TABLE(id INT,parent_id INT,name VARCHAR(100));
INSERT INTO @tbl VALUES
 (11,NULL,'Company')
,(33,11,'Department 1')
,(44,33,'Department 2a')
,(55,33,'Department 2b')
,(66,44,'SubDep 2a');

- 递归CTE将在逐行级别构建XML片段 - SELECT将使用XML方法.nodes()ROW_NUMBER生成PIVOT

的列名
WITH recCTE AS
(
    SELECT id, parent_id,name,(SELECT name AS [*] FOR XML PATH('')) AS NameConcat 
    FROM @tbl WHERE parent_id IS NULL

    UNION ALL

    SELECT t.id,t.parent_id,t.name,recCTE.NameConcat + '</lvl><lvl>' + (SELECT t.name AS [*] FOR XML PATH(''))
    FROM @tbl AS t
    INNER JOIN recCTE ON recCTE.id=t.parent_id
)
SELECT p.*
FROM
(
    SELECT id
          ,parent_id
          ,name
          ,'Level' + REPLACE(STR(ROW_NUMBER() OVER(PARTITION BY id ORDER BY (SELECT NULL)),2),' ','0') AS HierarchyRank
          ,lvl.value(N'(./text())[1]','nvarchar(max)') AS HierarchyName
    FROM recCTE
    CROSS APPLY (SELECT CAST('<lvl>' + NameConcat + '</lvl>' AS XML) AS PreLevels ) AS Casted
    CROSS APPLY Casted.PreLevels.nodes(N'/lvl') AS A(lvl)
) AS tbl
PIVOT
(
    MAX(HierarchyName) FOR HierarchyRank IN(Level01,Level02,Level03,Level04,Level05,Level06,Level07,Level08,Level09)
) AS p;

结果

+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| id | parent_id | name           | Level01 | Level02        | Level03       | Level04   | Level05 |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 11 | NULL      | Company        | Company | NULL           | NULL          | NULL      | NULL    |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 33 | 11        | Department 1   | Company | Department   1 | NULL          | NULL      | NULL    |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 44 | 33        | Department 2a  | Company | Department   1 | Department 2a | NULL      | NULL    |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 55 | 33        | Department 2b  | Company | Department   1 | Department 2b | NULL      | NULL    |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 66 | 44        | SubDep 2a      | Company | Department   1 | Department 2a | SubDep 2a | NULL    |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+

如果您需要更多关卡,唯一需要在PIVOT部分添加更多列名...

答案 1 :(得分:0)

您可以计算每个级别的行,并将它们联合起来:

with MyCTE as (

select id, parent_id, name, null as level1, null as level2, null as level3
from table1 as root
where root.parent_id is null

union

select level1.id, level1.parent_id, root.name, level1.name as level1, null as level2, null as level3
from table1 as level1
     inner join table1 as root on root.id = level1.parent_id
where root.parent_id is null

union

select level2.id, level2.parent_id, root.name, level1.name as level1, level2.name as level2, null as level3
from table1 as level2
     inner join table1 as level1 on level1.id = level2.parent_id
     inner join table1 as root on root.id = level1.parent_id
where root.parent_id is null

union

select level3.id, level3.parent_id, root.name, level1.name as level1, level2.name as level2, level3.name as level3
from table1 as level3
     inner join table1 as level2 on level2.id = level3.parent_id
     inner join table1 as level1 on level1.id = level2.parent_id
     inner join table1 as root on root.id = level1.parent_id
where root.parent_id is null

)
select * from MyCTE

如果您需要更多级别,则需要添加更多带有其他联接的选择