sql - 将平面表解析为树的最有效/优雅的方法是什么?

标签 sql algorithm recursion tree hierarchical-data

假设您有一个存储有序树层次结构的平面表:

Id   Name         ParentId   Order
 1   'Node 1'            0      10
 2   'Node 1.1'          1      10
 3   'Node 2'            0      20
 4   'Node 1.1.1'        2      10
 5   'Node 2.1'          3      10
 6   'Node 1.2'          1      20

这是一个图表,其中我们有[id] Name。根节点 0 是虚构的。

                       [0] ROOT
                          /    \ 
              [1] Node 1          [3] Node 2
              /       \                   \
    [2] Node 1.1     [6] Node 1.2      [5] Node 2.1
          /          
 [4] Node 1.1.1

您将使用哪种简约方法将其作为正确排序、正确缩进的树输出到 HTML(或文本,就此而言)?

进一步假设您只有基本的数据结构(数组和 HashMap ),没有带有父/子引用的奇特对象,没有 ORM,没有框架,只有您的两只手。该表表示为一个结果集,可以随机访问。

伪代码或者简单的英文都行,这纯粹是个概念题。

红利问题:有没有更好的方法来在 RDBMS 中存储这样的树结构?


编辑和添加

回答一位评论者(Mark Bessey 的)问题:根节点不是必需的,因为它永远不会显示。 ParentId = 0 是表达“这些是顶级”的约定。 Order 列定义了如何对具有相同父节点的节点进行排序。

我所说的“结果集”可以被描绘成 HashMap 数组(保留在该术语中)。因为我的例子本来就已经存在了。有些答案更进一步并首先构建它,但这没关系。

树可以任意深。每个节点可以有 N 个 child 。不过,我并没有完全想到“数百万条目”树。

不要将我选择的节点命名(“节点 1.1.1”)误认为是可以依赖的东西。这些节点同样可以称为“Frank”或“Bob”,没有暗示命名结构,这只是为了使其可读。

我已经发布了我自己的解决方案,所以你们可以把它分解成碎片。

最佳答案

现在MySQL 8.0 supports recursive queries ,我们可以说 all popular SQL databases support recursive queries在标准语法中。

WITH RECURSIVE MyTree AS (
    SELECT * FROM MyTable WHERE ParentId IS NULL
    UNION ALL
    SELECT m.* FROM MyTABLE AS m JOIN MyTree AS t ON m.ParentId = t.Id
)
SELECT * FROM MyTree;

我在演示文稿中测试了 MySQL 8.0 中的递归查询 Recursive Query Throwdown 2017 年。

以下是我 2008 年的原始回答:


有几种方法可以在关系数据库中存储树结构数据。您在示例中显示的内容使用两种方法:

  • 邻接列表(“父”列)和
  • 路径枚举(您的姓名列中的虚线数字)。

另一种解决方案称为嵌套集,它也可以存储在同一个表中。阅读 Joe Celko 的“Trees and Hierarchies in SQL for Smarties”,了解有关这些设计的更多信息。

我通常更喜欢一种称为闭包表(又名“邻接关系”)的设计来存储树结构数据。它需要另一个表,但是查询树非常容易。

我在演示文稿中介绍了闭包表 Models for Hierarchical Data with SQL and PHP在我的书中 SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming .

CREATE TABLE ClosureTable (
  ancestor_id   INT NOT NULL REFERENCES FlatTable(id),
  descendant_id INT NOT NULL REFERENCES FlatTable(id),
  PRIMARY KEY (ancestor_id, descendant_id)
);

将所有路径存储在闭包表中,其中存在从一个节点到另一个节点的直接祖先。为每个节点包含一行以引用自身。例如,使用您在问题中显示的数据集:

INSERT INTO ClosureTable (ancestor_id, descendant_id) VALUES
  (1,1), (1,2), (1,4), (1,6),
  (2,2), (2,4),
  (3,3), (3,5),
  (4,4),
  (5,5),
  (6,6);

现在你可以像这样得到一棵从节点 1 开始的树:

SELECT f.* 
FROM FlatTable f 
  JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1;

输出(在 MySQL 客户端中)如下所示:

+----+
| id |
+----+
|  1 | 
|  2 | 
|  4 | 
|  6 | 
+----+

换句话说,节点 3 和 5 被排除在外,因为它们是单独层次结构的一部分,而不是从节点 1 下降。


回复:来自 e-satis 关于直系子女(或直系 parent )的评论。您可以将“path_length”列添加到 ClosureTable 中,以便更轻松地专门查询直系子级或父级(或任何其他距离)。

INSERT INTO ClosureTable (ancestor_id, descendant_id, path_length) VALUES
  (1,1,0), (1,2,1), (1,4,2), (1,6,1),
  (2,2,0), (2,4,1),
  (3,3,0), (3,5,1),
  (4,4,0),
  (5,5,0),
  (6,6,0);

然后您可以在搜索中添加一个术语以查询给定节点的直接子节点。这些是 path_length 为 1 的后代。

SELECT f.* 
FROM FlatTable f 
  JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
  AND path_length = 1;

+----+
| id |
+----+
|  2 | 
|  6 | 
+----+

来自@ashraf 的评论:“[按名称] 对整棵树进行排序怎么样?”

这是一个示例查询,用于返回节点 1 的所有后代节点,将它们连接到包含其他节点属性(例如 name)的 FlatTable,并按名称排序。

SELECT f.name
FROM FlatTable f 
JOIN ClosureTable a ON (f.id = a.descendant_id)
WHERE a.ancestor_id = 1
ORDER BY f.name;

来自@Nate 的评论:

SELECT f.name, GROUP_CONCAT(b.ancestor_id order by b.path_length desc) AS breadcrumbs
FROM FlatTable f 
JOIN ClosureTable a ON (f.id = a.descendant_id) 
JOIN ClosureTable b ON (b.descendant_id = a.descendant_id) 
WHERE a.ancestor_id = 1 
GROUP BY a.descendant_id 
ORDER BY f.name

+------------+-------------+
| name       | breadcrumbs |
+------------+-------------+
| Node 1     | 1           |
| Node 1.1   | 1,2         |
| Node 1.1.1 | 1,2,4       |
| Node 1.2   | 1,6         |
+------------+-------------+

今天有用户建议修改。所以版主批准了编辑,但我正在撤销它。

编辑建议上面最后一个查询中的 ORDER BY 应该是 ORDER BY b.path_length, f.name,大概是为了确保顺序与层次结构匹配。但这不起作用,因为它会在“节点 1.2”之后排序“节点 1.1.1”。

如果您希望排序以合理的方式匹配层次结构,那是可能的,但不能简单地按路径长度排序。例如,请参阅我对 MySQL Closure Table hierarchical database - How to pull information out in the correct order 的回答.

关于sql - 将平面表解析为树的最有效/优雅的方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/192220/

相关文章:

java - 自然数使用递归的数字和

sql - 为什么存储过程不能与 select、where 和 having 一起使用

mysql - 如何从 ubuntu 终端创建 .sql 文件

c++ - C++ 中的对称矩阵

algorithm - 由部首定义的数字的正常形式?

javascript - 如何将使用全局变量的递归函数转换为纯函数?

mysql - 有没有办法只对行中的整数求和,然后对没有整数的字符串求和

mysql - 匹配 MySQL 中的单词 "Different"( bool 模式)

algorithm - 存在大小为 NxN 的 Hadamard 矩阵

javascript - Firefox、Chrome、Safari、IE 等的 js 递归限制是什么?