父子关系怎么弄,数据库表里到底该怎么设置才合适呢?
- 问答
- 2026-01-23 13:31:21
- 3
要弄清楚数据库表里父子关系怎么设置,其实核心就是解决一个问题:怎么让一条记录能“知道”自己的“爸爸”是谁,并且我们能方便地找到它所有的“儿子”和“孙子”。
最常用、最经典的方法叫做“邻接表”,这个方法理解起来特别简单,就是在同一张表里,加一个特殊的字段,这个字段的作用就是用来存储当前这条记录的“父亲”的ID。
比如说,我们有一张部门表,公司有总部,总部下面有技术部、市场部,技术部下面又有前端组、后端组,用邻接表来设计,这张表大概会长这样:
| 部门ID (id) | 部门名称 (name) | 父部门ID (parent_id) |
|---|---|---|
| 1 | 总部 | NULL |
| 2 | 技术部 | 1 |
| 3 | 市场部 | 1 |
| 4 | 前端组 | 2 |
| 5 | 后端组 | 2 |
你看,这样设计就非常清晰了。“父部门ID”这个字段,指向了这张表自身的“部门ID”,总部是顶级,没有父亲,所以它的 parent_id 是空的(NULL),技术部的 parent_id 是 1,说明它的父亲是ID为1的总部,前端组的 parent_id 是 2,说明它归技术部管。
这种设计的好处很明显,它非常符合直觉,一看就懂,增加一个子部门或者修改一个部门的归属非常容易,比如要把后端组从技术部调到市场部,你只需要把后端组那条记录的 parent_id 从 2 改成 3 就行了,非常简单。

邻接表也有个很大的麻烦事:查询一个节点的所有后代(包括儿子、孙子、曾孙子……)会非常费劲,而且效率不高。 我想查询“总部”下面所有的部门(包括技术部、市场部、前端组、后端组),用简单的SQL语句很难直接做到,通常需要借助数据库的高级功能(比如递归查询,但不是所有数据库都支持,或者支持得不好),或者是在程序代码里写循环,一次次地查询,这样如果层级很深,对数据库的压力就很大,速度也慢。
有没有办法能解决这个“找子孙”的难题呢?有,这就引出了另一种方法,叫做“路径枚举”,这个方法是在邻接表的基础上,再增加一个字段,用来记录从根节点到当前节点的完整路径。
还是用部门的例子,表结构会变成这样:
| 部门ID (id) | 部门名称 (name) | 父部门ID (parent_id) | 路径 (path) |
|---|---|---|---|
| 1 | 总部 | NULL | /1/ |
| 2 | 技术部 | 1 | /1/2/ |
| 3 | 市场部 | 1 | /1/3/ |
| 4 | 前端组 | 2 | /1/2/4/ |
| 5 | 后端组 | 2 | /1/2/5/ |
这个“路径”字段就像文件的绝对路径一样,我想找“技术部”下面的所有子部门,SQL语句就可以写得非常简单:SELECT * FROM 部门表 WHERE path LIKE ‘/1/2/%’,这样就能一下子把前端组和后端组都查出来,效率非常高。

路径枚举的优点是查询子孙节点非常快、非常方便,但它的缺点也很突出:一是维护起来麻烦,每当你要移动一个节点(比如又把后端组挪到市场部),你不仅要改它的 parent_id,还得更新它自己以及它所有子孙的 path 字段,这个操作很容易出错。二是路径的长度是有限的,如果你的层级非常非常深,可能会超出字段设定的长度。
除了上面两种,还有一种更强大的方法叫“闭包表”,这个方法有点“用空间换时间”的意思,它需要额外创建一张表,专门用来存储节点之间的所有祖先-后代关系,而不仅仅是直接的父子关系。
我们还是用部门表,但这次部门表本身变简单了,只保留ID和名称:
部门表 (departments) | 部门ID (id) | 部门名称 (name) | | :--- | :--- | | 1 | 总部 | | 2 | 技术部 | | 3 | 市场部 | | 4 | 前端组 | | 5 | 后端组 |

我们额外创建一张叫“部门关系表 (department_relations)”的表:
| 祖先ID (ancestor_id) | 后代ID (descendant_id) | 距离 (distance) |
|---|---|---|
| 1 | 1 | 0 |
| 1 | 2 | 1 |
| 1 | 3 | 1 |
| 1 | 4 | 2 |
| 1 | 5 | 2 |
| 2 | 2 | 0 |
| 2 | 4 | 1 |
| 2 | 5 | 1 |
| 3 | 3 | 0 |
| 4 | 4 | 0 |
| 5 | 5 | 0 |
这张表记录了所有关系,祖先ID是1(总部),后代ID是4(前端组),距离是2,表示总部是前端组的“爷爷”,距离为0表示自己和自己。
这种设计的强大之处在于,无论你想查什么关系,都变得异常简单,查子节点?查所有后代?查祖先链?都可以通过在这张关系表上做简单的连接查询完成,效率非常高,但代价就是需要维护一张额外的表,每次增加、删除、移动节点时,都需要同步更新这张关系表,逻辑上是最复杂的。
到底该怎么选呢?这完全取决于你的实际需求,根据在知乎等技术社区常见的讨论,可以总结如下:
- 如果你的层级关系很简单,固定只有一两层(比如商品分类,通常就两级:大类和小类),那么用最简单的邻接表就足够了,省心省力。
- 如果你的层级比较深,但结构非常稳定,不经常变动,并且你需要频繁地查询子树,那么路径枚举是一个不错的折中方案。
- 如果你的系统需要处理非常复杂的、深层次的、并且经常需要灵活查询和变动的树形结构,那么即使前期麻烦点,也强烈建议使用闭包表,它能为你后续的开发省去无数麻烦。
就是看你的业务场景在“简单易懂”、“查询效率”和“维护复杂度”之间更看重哪一点,没有绝对最好的方法,只有最适合你当前情况的方法。
本文由酒紫萱于2026-01-23发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/84482.html
