工作中遇到这样一个需求:

部门分为三层,从上往下依次是:一级部门、二级部门、三级部门,要求用户搜索三级部门的名称,然后返回该三级部门,以及它所在的一级部门和二级部门。如下图所示:

这个需求的实现很简单:先查询出三级部门,再分别查询它所在的二级部门、一级部门,就可以得到前端所需的结果。

但是在 Code Review 时,组长提出了一个问题:如果部门等级更深了,比如增加到5级、10级,这个实现方式是不是要查询5次、10次?这个时候该怎么实现?

这样,问题就变成了一个 MySQL 多级查询的问题,与前面三级查询问题相比,问题更复杂了,但是解决方式更简单了。

我们新建部门关系数据表:

 CREATE TABLE `dept` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(32) NOT NULL COMMENT '名称',
   `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父级ID',
   `level` tinyint(4) NOT NULL DEFAULT '1' COMMENT '等级,从1开始',
   `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
   `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   PRIMARY KEY (`id`),
   KEY `idx_parent_id` (`parent_id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='部门关系表';

查询某个三级部门的时候,过程如下:

1、查询出三级部门 dept3;

2、取出 dept3 的 parent_id 值,查询 dept3 的父级部门 dept2;

3、取出 dept2 的 parent_id 值,查询 dept2 的父级部门 dept1;

4、把三者组织成一个树状图,返回给前端。

该方法代码实现很简单,但是在部门等级加深时,该方法就不使用了,要不断重复第2步和第3步,直到查询出部门数据的 parent_id == 0 为止。

这就需要使用多级查询的方法来实现。

网上的一些文章,提出使用 MySQL 子查询和联表查询来实现多级查询,但是在线上环境最好避免联表查询,以免带来性能问题。最好的方式,是“参考阅读1”提出的方法:新建一个表,来记录所有部门节点的层级关系。

部门层级关系表DDL:

 CREATE TABLE `dept_depth` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `ancestor_id` int(11) NOT NULL DEFAULT '0' COMMENT '祖先节点ID',
   `dept_id` int(11) NOT NULL DEFAULT '0' COMMENT '当前部门节点ID',
   `depth` tinyint(4) NOT NULL DEFAULT '0' COMMENT '深度',
   `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
   PRIMARY KEY (`id`),
   KEY `idx_dept_id` (`dept_id`) USING BTREE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='部门层级表';

表中 depth 字段表的是:当前部门节点 dept_id 相对于它的祖先节点 ancestor_id 的深度,任何一个节点相对于自身的深度都是 0。

有了 dept_depth 表之后,查询一个 N 级部门的组织数据,过程如下:

1、从 dept 表查询出部门 deptN;

2、根据 deptN 的 id 值,到 dept_depth 表查询出它的所有祖先节点和深度记录:

 SELECT * FROM dept_depth WHERE dept_id = #{deptN.id};

3、从层级关系记录中,取出祖先节点 id 集合,再到 dept 表中取出所有祖先节点数据:

 SELECT * FROM dept  WHERE id IN(xxx);

4、把所有数据组织成一个树状图,返回给前端。

可以看出,使用该方法,无论部门层级深度是多少,代码的实现步骤都是相同的,区别在于第2步和第3步中查询的祖先节点的数量。

代码实现:github.com/ShiMengjie/…

如果我们用一棵树来记录部门之间的层级关系,那么多级查询问题就变成了:从叶子节点查询出一条到达根节点的路径,如下图所示:

从一个叶子节点恢复出它所在的树(部分树),与 LeetCode 的问题 《1028. 从先序遍历还原二叉树》很相似。

在 LeetCode 的问题中,使用先序遍历的字符串记录了每个节点的深度,然后才能恢复出二叉树;同理,在多级查询中,也需要记录路径上每个节点的深度,才能从叶子节点恢复出一条路径,这就是节点的深度表 dept_depth 的作用。

1、不要局限于业务需求,不要业务方要求做什么就只做什么,而是要加深对问题的理解。比如按照 ”三级查询“ 的实现方式,代码的复杂度是与部门层级深度成正比,复杂度是 O(N),而使用多级查询的方式之后,代码的复杂度是 O(1);

2、如果把“多级查询”看做是树的遍历恢复问题,需要知道路径上每个节点及其深度,才能从叶子结点恢复出它所在的路径。

1、sql之多级菜单设计查询优化

2、1028. 从先序遍历还原二叉树

3、【CodeGo】二叉树遍历问整理

原文链接:mp.weixin.qq.com/s/F4dlKBcYl…

分类:
后端
标签: