假设您有一个存储有序树层次结构的平面表:
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] 根
/ \
[1] 节点 1 [3] 节点 2
/ \ \
[2] 节点 1.1 [6] 节点 1.2 [5] 节点 2.1
/
[4] 节点 1.1.1
您将使用什么简约方法将其作为正确排序、正确缩进的树输出到 HTML(或文本)?
进一步假设你只有基本的数据结构(数组和哈希图),没有带有父/子引用的花哨对象,没有 ORM,没有框架,只有你的两只手。该表表示为一个结果集,可以随机访问。
伪代码或纯英文都可以,这纯粹是一个概念问题。
额外的问题:有没有更好的方法在 RDBMS 中存储这样的树结构?
编辑和添加
回答一位评论者( Mark Bessey )的问题:根节点不是必需的,因为它永远不会被显示。 ParentId = 0 是表达“这些是顶级”的约定。 Order 列定义了如何对具有相同父节点的节点进行排序。
我所说的“结果集”可以被描绘成一个哈希图数组(保留在那个术语中)。因为我的例子本来就应该在那里。有些答案会加倍努力并首先构建它,但这没关系。
树可以任意深。每个节点可以有 N 个孩子。不过,我并没有完全想到“数百万个条目”树。
不要将我选择的节点命名(’Node 1.1.1’)误认为是可以依赖的东西。这些节点同样可以称为“Frank”或“Bob”,没有暗示命名结构,这只是为了使其可读。
我已经发布了我自己的解决方案,因此你们可以将其分解。
原文由 Tomalak 发布,翻译遵循 CC BY-SA 4.0 许可协议
既然 MySQL 8.0 支持递归查询,我们可以说 所有流行的 SQL 数据库都支持标准语法的递归查询。
我在 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。
将所有路径存储在闭包表中,其中存在从一个节点到另一个节点的直接祖先。为每个节点包含一行以引用自身。例如,使用您在问题中显示的数据集:
现在你可以得到一个从节点 1 开始的树,如下所示:
输出(在 MySQL 客户端中)如下所示:
换句话说,节点 3 和 5 被排除在外,因为它们是单独层次结构的一部分,而不是从节点 1 下降。
回复:来自 e-satis 关于直系子女(或直系父母)的评论。您可以在 — 中添加“
path_length
ClosureTable
列,以便更轻松地专门查询直系子女或父母(或任何其他距离)。然后,您可以在搜索中添加一个术语来查询给定节点的直接子节点。这些是
path_length
为 1 的后代。来自@ashraf 的重新评论:“如何[按名称] 对整棵树进行排序?”
这是一个示例查询,用于返回节点 1 的所有后代节点,将它们连接到包含其他节点属性(例如
name
)的 FlatTable,然后按名称排序。来自@Nate的重新评论:
一位用户今天提出了修改建议。 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 的 回答。