点击上方“码农突围”,马上关注

这里是码农充电第一站,回复“666”,获取一份专属大礼包
真爱,请设置“星标”或点个“在看”

文章来源:https://sourl.cn/aCCTwr

| 目录

  • 问题来了

  • 查出所有子孙部门

  • 查询子孙部门总数

  • 判断是否叶子节点

  • 要不试试这个方法?

  • 查出所有子孙部门

  • 查询子孙部门总数

  • 判断是否叶子节点

  • 其他基本操作

  • 完结


通常树形结构的存储,是在子节点上存储父节点的编号来确定各节点的父子关系,例如这样的组织结构:

与之对应的表数据(department):

部门表结构(department)

id          部门编号
name        部门名称
level       所在树层级
parent_id   上级部门编号

| 问题来了

这样的方式很不错,可以很直观的体现各个节点之间的关系,通常可以满足大多数需求。但是当业务需求变得多了,数据量庞大了,这样的方式就不再适合用于生产。

例如:PM加了以下需求:

  1. 查出指定部门下所有子孙部门

  2. 查询子孙部门总数

  3. 判断节点是否叶子节点

查出所有子孙部门

使用指定部门编号,一层一层使用递归往下查,可能是多数人会想到的方法。尽管在mysql8.0支持了 cte(公共表表达式),递归效率比传统递归方式有明显提升,但是查询效率仍会随着部门树层级深度的提高而变差。

另外一种方法,一次性查出所有数据,放入内存中处理(数据量少时,可以选用。数据量多,不怕挨打的人也可以选这种)~

查询子孙部门总数

递归查询每一层的数量,最后相加。

判断是否叶子节点

方法1:可以加字段 isLeaf 的方式,来表示这个节点是否是叶子节点。

方法2:直接通过查询parent_id=当前id的count是否大于0,大于0表示不是叶子节点,等于0表示为叶子节点。

在日常中,可能会经常使用上述类似方法去解决类似的问题,但我觉得这样的方法在效率上不是最优解。于是乎开始查找更好的方案去解决这些问题。

| 要不试试这个方法?

直到后面查到国外一博客中,见到了所谓的《改进后的先序树遍历》文章(天哪,竟然是一篇2003年发表的文章)~

他具体是怎么做的呢?

还是回到刚刚的组织架构

我们从根节点开始,给董事长左值设为1,下级部门总经理左值设为2,以此类推地沿着边缘开始遍历,给每个节点加上左值,遇到叶子节点处给节点加上右值,再继续向上沿着边缘继续遍历,遍历结束回到根节点右侧,你将得到类似这样的结构。

遍历完后每一个节点都有与之对应的左右值。这个时候可以去除parent_id字段,添加lft,rgt,来存储左右值。

数据和结构准备完毕,我们来试试操作解决上面的需求~

查出所有子孙部门

根据当前表结构的规律,可以发现,要想查出所有子孙部门,只要查左值在 被查寻部门的左\右数之间的节点,查出来都是他的子节点。例如:查询行政总监的所有子部门,行政总监的左右数是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都解决了,查总数自然也就解决了,直接上select count就可以了,确实没有错,但是没有那个必要,因为有个简单公式可以直接计算。

公式:总数 = (右值 - 左值 - 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,因此他不是叶子节点。

至此已经完美的解决了上述需求问题,接下来再尝试一下业务的基本操作。

| 其他基本操作

新增部门

当新增一个部门时,需要对新增节点位置的后续边缘进行加2操作,因为每一个节点有左右两个数值。这个操作通常需要放到事务中进行处理。例如:在研发部门下添加一个新部门:

对应sql:

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操作。例如:删除刚刚添加的新部门:

对应sql

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*/

查询直接子部门

查询某部门的直接子部门(即不包含孙子部门),例如:查询总经理下的直接子部门。正常需要返回产品部和行政总监

对应的sql

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;

树形数据展示(JS示例)

let list = [//模拟sql查出来的列表。{id:1,name:'root',lft:1,rgt:8,level:1},{id:2,name:'child',lft:2,rgt:7,level:2},{id:3,name:'grandson',lft:3,rgt:4,level:3},{id:4,name:'grandson2',lft:5,rgt:6,level:3}
];
let rights = [] /*类似于一个栈结构(后进先出)*/
let mp = {}
//list.sort((a,b) => a.lft - b.lft)//如果你在sql中没有进行排序,需要在这里给他排序。
list.forEach(item => {if(rights.length > 0) {while(rights[rights.length-1] < item.rgt) {rights.splice(-1, 1)//从rights末尾去除}}let _level = rights.length;item._level = _level;mp[_level] = item.iditem.parent_id = _level - 1 in mp ? mp[_level - 1] : null;//计算出上级部门编号item.is_leaf = item.lft === item.rgt - 1;//判断是否叶子部门rights.push(item.rgt)
})/*上级部门计算出来了,和存parent_id的效果就一样了,后面只需要递归即可*/
/*递归函数 示例*/
let recursive = (_list, parent_id = null) => {let _tree = [];_list.forEach(item => {if(item.parent_id == parent_id) {let childs = recursive(_list, item.id)_tree.push({...item,children: childs.length > 0 ? childs : (item.isLeaf ? null : [])})}})return _tree
}
console.log(recursive(list))

| 完结

在我目前看来,这个方法的唯一缺点就是,每一次的新增或删除,操作节点的后续边缘走到的节点都要加/减2操作。

(完)
码农突围资料链接1、卧槽!字节跳动《算法中文手册》火了,完整版 PDF 开放下载!
2、计算机基础知识总结与操作系统 PDF 下载
3、艾玛,终于来了!《LeetCode Java版题解》.PDF
4、Github 10K+,《LeetCode刷题C/C++版答案》出炉.PDF欢迎添加鱼哥个人微信:smartfish2020,进粉丝群或围观朋友圈。

超赞 ! 老外的一种避免递归查询所有子部门的树数据表设计与实现!相关推荐

  1. 超赞,老外的一种避免递归查询所有子部门的树数据表设计与实现!

    点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 每天 14:00 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java ...

  2. mysql表的设计几种方式_支持多种登录方式的数据表设计 | 六阿哥博客

    一个带有用户系统的应用最基本登录方式是站内账号登录,但这种方式往往不能满足我们的需求.现在的应用基本都有站内账号.email.手机和一堆第三方登录,那么如果需要支持这么多种登录方式,或者还有银行卡登录 ...

  3. 一种多层级机构数据库表设计的思路及组织机构树数据库表设计

    在实际开发过程中,经常存在多个层级结构的设计,而且多个层级结构还需要排序.这里通过将多级结构的数据在同一张表中(无需多张表进行关联),并通过level的巧妙设计来实现单表查询. level的设计原则: ...

  4. 超赞网站推荐_字体(更多)超赞-标志性发明

    超赞网站推荐 by Pubudu Dodangoda 通过Pubudu Dodangoda 字体(更多)超赞-标志性发明 (Font (More) Awesome - an iconic invent ...

  5. 超赞的PyTorch资源大列表,GitHub标星9k+,中文版也上线了

    点击阅读原文,快速报名! 作者 | 红色石头 来源 | AI有道(ID: redstonewill) 自 2017 年 1 月 PyTorch 推出以来,其热度持续上升.PyTorch 能在短时间内被 ...

  6. 超赞的贪吃蛇、吃豆人和数字华容道等童年小游戏1行Python代码就能玩

    今天分享一个有趣的Python游戏库freegames,它包含20余款经典小游戏,像贪吃蛇.吃豆人.乒乓.数字华容道等等,依托于标准库Turtle. 我们不仅可以通过1行代码进行重温这些童年小游戏,还 ...

  7. github总star超9K!一个超赞的 PyTorch 资源大列表,有人把它翻译成了中文版!

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 转自:程序员爱码士 自 2017 年 1 月 PyTorch 推出以来,其热度持续上升 ...

  8. Github标星9k+,超赞的 PyTorch 资源大列表!

    关注上方"深度学习技术前沿",选择"星标公众号", 资源干货,第一时间送达! 原来的英文版 GitHub 项目地址: https://github.com/bh ...

  9. 超赞的 PyTorch 资源大列表,有人把它翻译成了中文版!

    点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 自 2017 年 1 月 PyTorch 推出以来,其热度持续上升.PyTorch 能在短时间 ...

  10. Science上发表的超赞聚类算法

    论文. Clustering by fast search and find of density peak. Alex Rodriguez, Alessandro Laio 参考链接:Science ...

最新文章

  1. win10子系统ubuntu文件夹位置_win10子系统(WSL)自定义安装路径
  2. awk算术运算一例:统计hdfs上某段时间内的文件大小
  3. SMB(Server Message Block) Protocal Research
  4. 后端开发工程师的DIV+CSS两栏布局入门
  5. Polycarp Restores Permutation
  6. FileNotFoundError: [Errno 2] No such file or directory: 'test/条形图03.html'
  7. java for each 原理_Java for each实现机制代码原理解析
  8. 是时候详细探究webview了
  9. C语言 动态开辟内存管理
  10. 用户体验的13条金科玉律
  11. mysql表分片语法,分布式事务数据库HotDB的HINT特色语法
  12. maven项目的构建
  13. java异常处借接错书_利用Java异常机制实现模拟借书系统
  14. 如何把一张图片做成一个好看的电脑图标
  15. 对区块链撒谎:将“垃圾进,垃圾出”问题应用在去中心化网络上
  16. 等差素数列(java)
  17. html js 打印 图片不显示 canvas
  18. (2) [保护模式]段描述符
  19. 社群裂变营销“肩负”着公司百分之九十的流量
  20. 微软发布Win11 2022最大更新22H2 版本号为 22621.521

热门文章

  1. php把时间加一天,php如何在某個時間上加一天?一小時? 時間加減(轉)
  2. 计算机科学家图灵,伟大的计算机科学家图灵
  3. 单页面应用(SPA)与多页面应用(MPA)的区别对比
  4. 设计模式(6)——命令模式
  5. C++11常用特性的使用经验总结-概述及目录
  6. ML/DL-复习笔记【三】- 算法的评价指标
  7. 曲线在三维空间的旋转计算以及Matlab实例实现
  8. 人工智能知识体系的学习路线(南京大学人工智能学院本科生培养体系)
  9. redission分布式锁
  10. JSON 解析之 GSON