
目录:
数据表结构设计
1.1 传统设计
1.2 高效查询设计
三个示例场景
2.1 查询某个节点的所有子孙节点
2.2 查询某个节点的子孙部门总数
2.3 判断是否为叶子节点
CRUD
3.1 新增部门
3.2 删除部门
3.3 查询直接子部门
3.4 查询祖链路径
参考/来源:
数据表结构设计
传统设计
对于部门数据表,常见的表结构设计如下:
id 部门编号
name 部门名称
level 所在树层级
parent_id 上级部门编号
在数据表插入数据后,可以形成一个树形的数据表结构。在数据量大或者业务复杂的时候,递归去查询数据,会造成一定的性能问题。
| id | name | parent_id | level |
|---|---|---|---|
| 1 | 董事长 | 0 | 1 |
| 2 | 总经理 | 1 | 2 |
| 3 | 产品部 | 2 | 3 |
| 4 | 研发部 | 3 | 4 |
| 5 | 设计部 | 3 | 4 |
| 6 | 行政总监 | 2 | 3 |
| 7 | 财核部 | 6 | 4 |
| 8 | 会计 | 7 | 5 |
| 9 | 出纳 | 7 | 5 |
| 10 | 行政部 | 6 | 4 |
高效查询设计
从根节点开始,给董事长左值设为1,下级部门总经理左值设为2,以此类推地沿着边缘开始遍历,给每个节点加上左值,遇到叶子节点处给节点加上右值,再继续向上沿着边缘继续遍历,遍历结束回到根节点右侧,你将得到类似这样的结构:
对应的数据库表为:
| id | name | lft | rgt | level |
|---|---|---|---|---|
| 1 | 董事长 | 1 | 20 | 1 |
| 2 | 总经理 | 2 | 19 | 2 |
| 3 | 产品部 | 3 | 8 | 3 |
| 4 | 设计部 | 4 | 5 | 4 |
| 4 | 研发部 | 6 | 7 | 4 |
| 6 | 行政总监 | 9 | 18 | 3 |
| 7 | 财核部 | 10 | 15 | 4 |
| 8 | 出纳 | 11 | 12 | 5 |
| 9 | 会计 | 13 | 14 | 5 |
| 10 | 行政部 | 16 | 17 | 4 |
三个示例场景
查询某个节点的所有子孙节点
根据当前表结构的规律,可以发现,要想查出所有子孙部门,只要查左值在 被查寻部门的左\右数之间的节点,查出来都是他的子节点。例如:查询行政总监的所有子部门,行政总监的左右数是9和18,因此只需要用9和18做lft字段的between查询,查询出的结果就是【被查部门本身数据和所有子孙部门】;
SET @lft := 9;
SET @rgt := 18;
SELECT * FROM department WHERE lft BETWEEN @lft AND @rgt ORDER BY lft ASC;
/*例子中用BETWEEN将被查部门本身也查了出来。实际中可以用大于小于*/
查询某个节点的子孙部门总数
公式:总数 = (右值 - 左值 - 1) / 2
例如:
行政总监的子孙部门数 = (18 - 9 - 1) / 2 = 4
董事长的子孙部门数 = (20 - 1 - 1) / 2 = 9
会计的子部门数 = (14 - 13 - 1) / 2 = 0
判断是否为叶子节点
右值 - 1 == 左值那他就是叶子节点,或者左值 + 1 == 右值那他就是叶子节点,反之则不是叶子节点。
例如:
设计部,5 - 1 == 4,因此他是叶子节点。
董事长,20 - 1 != 1,因此他不是叶子节点。
CURD
新增部门
当新增一个部门时,需要对新增节点位置的后续边缘进行加2操作,因为每一个节点有左右两个数值。这个操作通常需要放到事务中进行处理。
新增或删除节点时右节点判断条件应该是 lft >= 当前节点的 lft。
SET @lft := 7;/*新部门的左值*/
SET @rgt := 8;/*新部门的左值*/
SET @level := 5;/*新部门的层级*/
begin;
/*将插入的后续边缘的节点左右数+2*/
UPDATE department SET lft=lft+2 WHERE lft > @lft;
UPDATE department SET rgt=rgt+2 WHERE rgt >= @lft;
/*插入数据*/
INSERT INTO department(name,lft,rgt,level) VALUES('新部门',@lft,@rgt,level);
/*新增影响行数为0时,必须回滚*/
commit;
/*rollback;*/
删除部门
删除部门与新增部门类似,不同的是需要对删除节点的后续边缘节点减2操作。例如:删除刚刚添加的新部门:
SET @lft := 7;/*要删除的节点左值*/
SET @rgt := 8;/*要删除的节点右值*/
begin;
UPDATE department SET lft=lft-2 WHERE lft > @lft;
UPDATE department SET rgt=rgt-2 WHERE rgt > @lft;
/*删除节点*/
DELETE FROM department WHERE lft=@lft AND rgt=@rgt;
/*删除影响行数为0时,必须回滚*/
commit;
/*rollback*/
查询直接子部门
查询某部门的直接子部门(即不包含孙子部门),例如:查询总经理下的直接子部门。正常需要返回产品部和行政总监。
SET @level := 2;/*总经理的level*/
SET @lft := 2;/*总经理的左值*/
SET @rgt := 19;/*总经理的右值*/
SELECT * FROM department WHERE lft > @lft AND rgt < @rgt AND level = @level+1;
查询祖链路径
查询某部门的祖链路径。例如:查询产品部的祖链路径,正常需要返回董事长,总经理
SET @lft := 3;/*产品部左值*/
SET @rgt := 8;/*产品部右值*/
SELECT * FROM department WHERE lft < @lft AND rgt > @rgt ORDER BY lft ASC;