既然MySQL 8.0 支持递归查询,我们可以说所有流行的 SQL 数据库都支持标准语法的递归查询。
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;
我在 2017 年的演示Recursive Query Throwdown中测试了 MySQL 8.0 中的递归查询。
以下是我 2008 年的原始答案:
有几种方法可以在关系数据库中存储树状结构的数据。您在示例中显示的内容使用两种方法:
- 邻接列表(“父”列)和
- 路径枚举(您的姓名列中的虚线数字)。
另一种解决方案称为嵌套集,它也可以存储在同一个表中。有关这些设计的更多信息,请阅读Joe Celko 的“ Smarties 中的树和层次结构”。
我通常更喜欢一种称为闭包表(又名“邻接关系”)的设计来存储树形结构的数据。它需要另一个表,但是查询树非常容易。
我在我的演示文稿Models for Hierarchical Data with SQL and PHP和我的书SQL Antipatterns: Avoiding the Pitfalls of Database Programming中介绍了Closure Table 。
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 关于直系子女(或直系父母)的评论。您可以向 中添加“ 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);
然后,您可以在搜索中添加一个术语来查询给定节点的直接子节点。这些是 1 的后代path_length
。
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 的所有后代节点,将它们连接到包含其他节点属性的 FlatTable,例如name
,并按名称排序。
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 |
+------------+-------------+
一位用户今天提出了修改建议。SO 版主批准了编辑,但我正在撤消它。
编辑建议上面最后一个查询中的 ORDER BY 应该是ORDER BY b.path_length, f.name
,大概是为了确保排序与层次结构匹配。但这不起作用,因为它会在“Node 1.2”之后订购“Node 1.1.1”。
如果您希望排序以合理的方式匹配层次结构,这是可能的,但不仅仅是通过路径长度排序。例如,请参阅我对MySQL Closure Table hierarchy database - How to pull information out in the correct order 的回答。