我可以从自引用(分层)表中获取树结构吗?


8

给定这样的层次表:

CREATE TABLE [dbo].[btree]
(
  id INT PRIMARY KEY
, parent_id INT REFERENCES [dbo].[btree] ([id])
, name NVARCHAR(20)
);

我想获得整个树结构。

例如,使用以下数据:

INSERT INTO [btree] VALUES (1, null, '1 Root');
INSERT INTO [btree] VALUES (2,    1, '1.1 Group');
INSERT INTO [btree] VALUES (3,    1, '1.2 Group');
INSERT INTO [btree] VALUES (4,    2, '1.1.1 Group');
INSERT INTO [btree] VALUES (5,    2, '1.1.2 Group');
INSERT INTO [btree] VALUES (6,    3, '1.2.1 Group');
INSERT INTO [btree] VALUES (7,    3, '1.2.2 Group');
INSERT INTO [btree] VALUES (8,    4, '1.1.1.1 Items');
INSERT INTO [btree] VALUES (9,    4, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (10,   5, '1.1.2.1 Items');
INSERT INTO [btree] VALUES (11,   5, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (12,   6, '1.2.1.1 Items');
INSERT INTO [btree] VALUES (13,   6, '1.2.1.2 Items');
INSERT INTO [btree] VALUES (14,   7, '1.2.2.1 Items');

我想获得:

+----+-----------+---------------------+
| id | parent_id | description         |
+----+-----------+---------------------+
|  1 |    NULL   | 1 Root              |
|  2 |     1     |   1.1 Group         |
|  4 |     2     |     1.1.1 Group     |
|  8 |     4     |       1.1.1.1 Items |
|  9 |     4     |       1.1.1.2 Items |
|  5 |     2     |     1.1.2 Group     |
| 10 |     5     |       1.1.2.1 Items |
| 11 |     5     |       1.1.2.2 Items |
|  3 |     1     |   1.2 Group         |
|  6 |     3     |     1.2.1 Group     |
| 12 |     6     |       1.2.1.1 Items |
| 13 |     6     |       1.2.1.2 Items |
|  7 |     3     |     1.2.2 Group     |
| 14 |     7     |       1.2.2.1 Items |
+----+-----------+---------------------+

我正在使用像这样的递归查询来获取记录:

;WITH tree AS
(
    SELECT c1.id, c1.parent_id, c1.name, [level] = 1
    FROM dbo.[btree] c1
    WHERE c1.parent_id IS NULL
    UNION ALL
    SELECT c2.id, c2.parent_id, c2.name, [level] = tree.[level] + 1
    FROM dbo.[btree] c2 INNER JOIN tree ON tree.id = c2.parent_id
)
SELECT tree.level, tree.id, parent_id, REPLICATE('  ', tree.level - 1) + tree.name AS description
FROM tree
OPTION (MAXRECURSION 0)
;

这是当前结果:

+----+-----------+---------------------+
| id | parent_id | description         |
|  1 |    NULL   | 1 Root              |
|  2 |     1     |   1.1 Group         |
|  3 |     1     |   1.2 Group         |
|  6 |     3     |     1.2.1 Group     |
|  7 |     3     |     1.2.2 Group     |
| 14 |     7     |       1.2.2.1 Items |
| 12 |     6     |       1.2.1.1 Items |
| 13 |     6     |       1.2.1.2 Items |
|  4 |     2     |     1.1.1 Group     |
|  5 |     2     |     1.1.2 Group     |
| 10 |     5     |       1.1.2.1 Items |
| 11 |     5     |       1.1.1.2 Items |
|  8 |     4     |       1.1.1.1 Items |
|  9 |     4     |       1.1.1.2 Items |
+----+-----------+---------------------+

我不知道如何按级别订购它。

有没有一种方法可以为每个子级别设置等级?

我已经建立了 Rextester

Answers:


7

添加一个“路径”字段,并按照类似于文件路径的顺序进行排序。正如ypercube所提到的,在此示例中,排序过于简单,并且碰巧可以正常工作,但为简单起见,我将保持原样。在大多数情况下,当我使用此模式时,我都会按名称而不是ID进行排序。

IF OBJECT_ID('[dbo].[btree]', 'U') IS NOT NULL 
    DROP TABLE [dbo].[btree];
GO

CREATE TABLE [dbo].[btree]
(
  id INT PRIMARY KEY
, parent_id INT REFERENCES [dbo].[btree] ([id])
, name NVARCHAR(20)
);
GO

INSERT INTO [btree] VALUES (1, null, '1 Root');
INSERT INTO [btree] VALUES (2,    1, '1.1 Group');
INSERT INTO [btree] VALUES (3,    1, '1.2 Group');
INSERT INTO [btree] VALUES (4,    2, '1.1.1 Group');
INSERT INTO [btree] VALUES (5,    2, '1.1.2 Group');
INSERT INTO [btree] VALUES (6,    3, '1.2.1 Group');
INSERT INTO [btree] VALUES (7,    3, '1.2.2 Group');
INSERT INTO [btree] VALUES (8,    4, '1.1.1.1 Items');
INSERT INTO [btree] VALUES (9,    4, '1.1.1.2 Items');
INSERT INTO [btree] VALUES (10,   5, '1.1.2.1 Items');
INSERT INTO [btree] VALUES (11,   5, '1.1.2.2 Items');
INSERT INTO [btree] VALUES (12,   6, '1.2.1.1 Items');
INSERT INTO [btree] VALUES (13,   6, '1.2.1.2 Items');
INSERT INTO [btree] VALUES (14,   7, '1.2.2.1 Items');

;WITH tree AS
(
    SELECT c1.id, c1.parent_id, c1.name, [level] = 1, path = cast('root' as varchar(100))
    FROM dbo.[btree] c1
    WHERE c1.parent_id IS NULL
    UNION ALL
    SELECT c2.id, c2.parent_id, c2.name, [level] = tree.[level] + 1, 
           Path = Cast(tree.path+'/'+right('000000000' + cast(c2.id as varchar(10)),10) as varchar(100))
    FROM dbo.[btree] c2 INNER JOIN tree ON tree.id = c2.parent_id
)
SELECT tree.path, tree.id, parent_id, REPLICATE('  ', tree.level - 1) + tree.name AS description
FROM tree
Order by path
OPTION (MAXRECURSION 0)
;

这里rextester


这是正确的主意,但在路径表达式中应c2.id替换为row_number并在左侧进行填充,以便所有部分的长度相等。否则,它将无法适用于所有数据。与数据55和顺序的变化只需更换2
ypercubeᵀᴹ

完全同意。我在移动设备上,希望赢得比赛的胜利:)实际上,我通常会在路径中使用“名称”字段。那通常是我的用例。
本·坎贝尔

行号(不是必需的)可能是我错的,但是填充是。+1(如果我们确实使用row_number,则路径将重建名称的第一部分!)
ypercubeᵀᴹ17年

我已对Path进行了小的修改,以添加填充。
ypercubeᵀᴹ

1
如果对最大深度有任何疑问,我通常使用两倍于我预期的路径长度。如果您知道ID / row_number的最大数量级,也可以减少零填充。
本·坎贝尔

4

作弊,只是一点点;)看,没有递归!

经过rextester.com测试

SELECT btree.*        -- , a,b,c,d     -- uncomment to see the parts
FROM btree 
  OUTER APPLY
    ( SELECT rlc = REVERSE(LEFT(name, CHARINDEX(' ', name)-1))) AS r
  OUTER APPLY
    ( SELECT a = CAST(REVERSE(PARSENAME(r.rlc, 1)) AS int),
             b = CAST(REVERSE(PARSENAME(r.rlc, 2)) AS int),
             c = CAST(REVERSE(PARSENAME(r.rlc, 3)) AS int),
             d = CAST(REVERSE(PARSENAME(r.rlc, 4)) AS int)
    ) AS p 
ORDER BY a, b, c, d ;

当然,以上是相当有限的。它仅在以下假设下起作用:

  • name列已存储(在第一部分中)实际的“路径”。
  • 树的深度最大为4(因此路径最多包含4个部分)。
  • CAST .. AS int只需要如果部件是数字。

说明:该代码通过使用该函数PARSENAME()来工作,该函数的主要目的是将一个对象名称分成四个部分:

Server.Database.Schema.Object
  |        |       |      |
 4th      3rd     2nd    1st

请注意,顺序相反。作为一个例子,PARSENAME('dbo.btree', 2)会给我们'dbo'的result.With 3,我们会得到NULL(这就是为什么REVERSE()在代码中使用了两次,否则我们会得到在一开始空值。该'1.2'会被解析成null, null, 1, 2,而我们希望1, 2, null, null。 )


结论:毕竟,我还要补充一点,鲍勃·坎贝尔的答案是要走的路,因为它更笼统,并产生了路径层次结构(在结果的“路径”列中),该路径层次结构随后可用于ORDER BY

您可能会考虑的其他选项-如果表的大小变大并且递归解决方案变慢-则实际上是将路径存储在单独的列中(采用适合排序的格式,即带有填充)或使用提供的HierarchyID正是适合此用例的类型,即分层数据。


:)真的很棒!不幸的name是在这种情况下不能使用。我要花一整夜的时间来解密它,我能解释一下吗?
McNets

那么,“名称”列中没有您在示例中提供的数据吗?可怜。
ypercubeᵀᴹ

不,我以它为例,只是说明有一些层次。
McNets

1
@Mcnets在(name确实)确实存储路径(带有文本)的情况下(如)'order173.palletA27.box9'.bag3A,您仍然可以使用代码(只需将类型转换为int)。在任何情况下,BenCambell的查询都是通常的方法。
ypercubeᵀᴹ

1
@EvanCarroll是的,hierarchyid类型。我只是在最后一节中添加了有关带有链接的其他选项的信息。
ypercubeᵀᴹ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.