对于类似票证的遗留系统,我有以下简化的数据库表结构。
messages
id INT
parent_id INT
content TEXT
answer TEXT
...
在列表中,我显示所有消息。当一条消息被点击时,我会显示它的答案等。
问题是,现在我需要制作一个与此消息相关的所有父和子的列表结构,以及此消息在树中的位置如何从数据库中检索这些
我使用的是Laravel,但原始SQL也可以帮助我找到方向。
示例:
╔════╦═══════════╦════════════════════════╦═════════════════╗║id║parent_id║所容纳之物║答复║╠════╬═══════════╬════════════════════════╬═════════════════╣║1.║NULL║嗨,我有个问题║我没办法║║2.║1.║问题仍然存在║好的,是什么?║║3.║2.║没关系,我明白了║哦,好吧。║║4.║3.║问题又回来了║哪个问题?║║5.║4.║同样的问题再次出现║ ... ║╚════╩═══════════╩════════════════════════╩═════════════════╝
当用id = 4
显示消息时,我应该能够显示类似以下列表的内容:
消息历史记录:
-嗨,我有个问题
-问题仍然存在
-无论如何,我得到了这个
-问题又回来了
-同样的问题再次出现
对于每个父级和子级,我只能想到一个循环和几个SQL查询执行,这看起来像是一种代码气味。
更新
正如Daan所说,这个问题似乎与如何创建MySQL层次递归查询重复。
然而,我决定不删除它,因为Ravan刚刚用Laravel的方法回答了它,帮助我解决了这个问题,所以我将把它留在这里供将来参考。
由于您正在执行分层操作,因此应该使用一种策略从数据库中保存和检索这些数据。
一种方法是使用嵌套集模型,这可以使它更容易。Laravel有一个很好的软件包,叫做etrepat/baum,它也解释了它的工作原理,我引用:
背后的理论,一个TL;DR版本
可视化嵌套集如何工作的一个简单方法是将所有嵌套集都看作父实体它的子树,以及它周围的父树,等等。所以这棵树:
root
|_ Child 1
|_ Child 1.1
|_ Child 1.2
|_ Child 2
|_ Child 2.1
|_ Child 2.2
可以这样可视化:
___________________________________________________________________
| Root |
| ____________________________ ____________________________ |
| | Child 1 | | Child 2 | |
| | __________ _________ | | __________ _________ | |
| | | C 1.1 | | C 1.2 | | | | C 2.1 | | C 2.2 | | |
1 2 3_________4 5________6 7 8 9_________10 11_______12 13 14
| |___________________________| |___________________________| |
|___________________________________________________________________|
数字表示左右边界。那么桌子可能看起来像这样:
id | parent_id | lft | rgt | depth | data
1 | | 1 | 14 | 0 | root
2 | 1 | 2 | 7 | 1 | Child 1
3 | 2 | 3 | 4 | 2 | Child 1.1
4 | 2 | 5 | 6 | 2 | Child 1.2
5 | 1 | 8 | 13 | 1 | Child 2
6 | 5 | 9 | 10 | 2 | Child 2.1
7 | 5 | 11 | 12 | 2 | Child 2.2
要获取父节点的所有子节点,您可以
SELECT * WHERE lft IS BETWEEN parent.lft AND parent.rgt
要得到孩子的数量,它是
(right - left - 1)/2
要使一个节点及其所有祖先返回到根,您需要
SELECT * WHERE node.lft IS BETWEEN lft AND rgt
正如您所看到的,查询是递归的,并且速度慢得令人望而却步普通的树突然变得很快。Nifty,不是吗?