在SQL中存储层次结构的一种非常有效的方法是使用某种“封闭表”。 编辑起来有点困难,并且会占用更多空间,但是如果您只对ID感兴趣,通常可以使用单个JOIN或单个查询来递归地进行旅行。
此表将为每个可能的祖先/后代关系包含1条记录,以及在实名表中anc=des=id
所保存的每个项目1条记录。
对于这棵树:
1
2 4 7
3 5 6
我们的SQL表将包含:
+-----+-----+------+-------+
| anc | des | diff | depth |
+-----+-----+------+-------+
| 1 | 1 | 0 | 0 |
| 1 | 2 | 1 | 0 |
| 2 | 3 | 1 | 1 |
| 1 | 3 | 2 | 0 |
| 1 | 4 | 1 | 0 |
| 2 | 5 | 1 | 1 |
| 1 | 5 | 2 | 0 |
| 4 | 6 | 1 | 1 |
| 1 | 6 | 2 | 0 |
| 1 | 7 | 1 | 0 |
| 2 | 2 | 0 | 1 |
| 3 | 3 | 0 | 2 |
| 4 | 4 | 0 | 1 |
| 5 | 5 | 0 | 2 |
| 6 | 6 | 0 | 2 |
| 7 | 7 | 0 | 1 |
+-----+-----+------+-------+
然后执行任务:“获取节点5的所有祖先”,换句话说,“节点5的路径”可以通过以下查询完成:
SELECT `anc` FROM `closure` WHERE `des` = 5
可以通过以下查询完成“获取节点1的所有后代”任务:
SELECT `des` FROM `closure` WHERE `anc` = 1
“获取节点1的所有直接后代”的过程如下:
SELECT `des` FROM `closure` WHERE `diff` = 1 AND `anc` = 1
最后,“获取所有根节点”的操作如下:
SELECT `anc` FROM `closure` WHERE `depth` = 0 AND `anc` = `des`
这四个任务共同构成了从树中选择事物的最有效方法。 但是,实际上,在对事物进行分类时,人们无法决定将事物放置在何处。不可避免地,需要某些东西才能在树中的多个位置结束。这在工作中投入了扳手。这些简单的查询中的两个不再起作用(N o 1和N o 2)。
请注意,为了防止堆栈溢出和递归问题,在本示例中,我们的图仍然是一种“树”,这是事实。没有循环。
第一个问题是“获取所有后代”现在有重复的结果。可以使用GROUP BY
子句来解决。
对我来说,第二个更难解决的问题是第一个 st 问题:现在有多个通往叶节点的可能路径。让我们将问题分为两个可能的令人满意的结果:
有没有一种方法可以在单个或固定数量的查询中使用单个或固定数量的JOIN来获取任意深的树,从而获得以下任一值:
规范路径
这是在树表示形式中到特定叶节点的节点数最少的路径。请注意,不一定要像数据结构中的示例那样对树进行“排序”,因为可以随意插入和删除节点。
所有路径
获取到特定叶节点的所有可能路径。
朴素方法失败的示例:
考虑这棵树:
1
2 3
5 4
6 6
问“ 6的上升点是什么”这个问题,应该有两个逻辑答案:
1-2-5-6和1-3-4-6。
但是,使用朴素的查询和排序,我们只能真正得到:
1-2-4-6或1-3-5-6。
两者实际上都不是有效路径。
在我读过的所有有关闭包表的教程中,都明确指出闭包表能够处理在多个位置出现相同项目的层次结构,但从未真正解释过如何正确执行此操作,只是给读者。但是,我遇到了一些琐碎的问题。