树形结构存储方法的选择

简单的方法跟踪多级回复构成的树形分支:parent_id

一开始的思路

  1. 使用parent_id跟踪分支
  2. 使用先找出所有节点,按照一定顺序整合成树形结构

缺陷:

  • 在深度过深时仅用parent_id需要执行很多次SQL才能获取给定主题的所有数据
  • 每天的数据变动可能非常大,每访问一次整合一次过于浪费时间且不切实际

目标:一个更好的存储多级结构及简单高效获取一个完整分支的方法

分层存储合查询

树形结构举例:组织架构图、线程化讨论

解决方案

邻接表

依赖父节点构成的邻接表,由parent_id指向另一个元素的id作为父节点

缺陷:无法查询一个节点的所有后代

可使用关联查询来获取一条评论和它的直接后代

select * from c1.*,c2.*
from Comments c1 left outer join Comments c2
on c2.parent_id = c1.id

只能获取两层数据

树的特性:可以任意深地扩展

缺陷:

  • 每想多查一层就得多一个连接,而SQL查询最多4层

  • 聚合函数的使用很困难例如count()

  • 可能只需部分子树信息/聚合信息

  • 删除困难,要删除某个节点困难必须删除某颗子树,通过多次查询找到所有后代 ,再从最低级逐个删除满足外键完整性,parent_id也是一个外键,只不过连接的表还是本表

  • 如果想删除某个节点并提升其子节点就必须修改子节点parent_id再删除这个节点

优势:

  • 增加一个叶子节点

    insert into Comments(bug_id,parent_id,author,comment)
    values(1234,7,'kukla','Thanks!')
    
  • 修改一个节点的位置或者一颗子树的位置

    update comments set parent_id = 3 where comment_id = 6
    
小结

简单,但有些操作例如删除、查询、提升子节点要付出额外代价,增加/修改简单

如果有一下问题,说明需要考虑其他实现方案

  1. 我们的树结构支持多少层

    在纠结不适用递归查询获取一个给定节点的所有祖先或者后代

    只支持有限层级操作,但多少层才能满足需求

  2. 我总是很怕解除那些管理树结构的代码

    每一项技术都能使一些任务变得很简单,但通常是以其他任务变得复杂为代价的,选择方案应该应对合适的场景

  3. 我需要一个脚本定期清理树种孤立节点的数据

    因为删除非叶子节点产生了一些迷失节点,采用其他数据结构后也要保证树结构的数据一致性

合理使用反模式

如果获取直接父子节点/插入新节点 如果这两个操作能满足现下所有需求,那么使用邻接表就能很好的工作了

不要过度设计

其他解决方案

路径枚举

解决邻接表获取祖先节点困难的问题

使用path字段将层级信息组合,例如/usr/local/lib这种类似的存储结构

可通过比较每个节点的路径来查询一个节点的祖先

缺陷:

  1. 数据库不能确保路径的格式总是正确或者路径种的节点确实存在。
  2. 依赖于应用程序的逻辑代码来维护路径的字符串,并且验证字符串的正确性的开销很大。
  3. 无论path设置为多大都存在长度限制,无法达到树形结构无限扩展的特性

优势:

  1. 很好的支持祖先的查找
  2. 支持深度查询

嵌套集

存储子孙节点的相关信息而不是节点的直接祖先。通过两个数字nsleft及nsright来存储

nsleft:所有小于该节点的后代id

nsright:所有大于该节点的后代id

这些数字和comment_id没有任何关系,通过深度遍历逐层分配nsleft的值,并在返回时依次递增地分配nsleft的值并在返回时一次递增地分配nsright的值。

根据他们来找到给定节点的祖先/后裔

栗子:

寻找4的所有后裔

select * from comment c1 join comment c2

on c2.nsleft between c1.nsleft and c2.nsright

where c1.comment_id = 4

优势:

  1. 想删除某个非叶子节点,它的后代会自动代替被删除的节点,称为其直接祖先节点的直接后代。节点虽然是有序分配,但删除一个并不会队范围查询造成影响。

缺陷:

  1. 获取直接父节点/后代变得比较复杂
  2. 对树进行操作,比如插入和移动比其他设计复杂得多,需要重新遍历计算n左右值

优势:

如果简单快速的查询是重要部门,嵌套集是最佳选择

频繁的插入、删除就不适合

闭包表

额外创建TreePaths的表,包含两列,每一列都是指向Comments种comment_id的外键

获取祖先/后代更加直接

获取评论4的后代:

等同于查询所有祖先节点是4的节点信息

select c.*

from comments as c

join treepaths as t on c.comment_id = t.descendant

where t.ancestor = 4;

获取6的祖先:

查询所有后裔为6的节点

select c.*

from comments c join treepaths t on c.comment_id = t.ancestor

where t.descendant = 6;

插入一个5的新的叶子节点:

首先插入一条自己到自己的关系,然后搜索treepaths 表中后代是评论5的节点,增加该节点和新插入节点的“祖先-后代”关系

然后插入新节点“祖先-后代”的关系

insert into treepaths (ancestor,decendant)

select t.ancestor,8

from treepaths as t

where t.descendant = 5

union all

select 8,8

删除一颗完整子树

删除评论4及其后代

删除所有在treepaths表中后代为4的行以及那些以评论#4的后代为后代的行

删除4及其子节点的关系,删除其他节点到4的子节点的关系

delete from treepaths

where descendant in (

select descendant from treepaths where ancestor = 4

)

这样的删除并非删除掉节点,只是删除了关系,把层次结构分离使得设计更加灵活

移动

要移动树则先端开子树与祖先们的关系:找到这颗子树的顶点,删除它的所有子节点和它的所有祖先节点之间的关系。

比如把6移动到3下

首先把自我引用删掉

delete from treepaths where descendant in (

​ select descendant from treepaths where ancestor = 6

)and ancestor in (

​ select ancestor from treepaths where descendant = 6 and ancestor != descendant

)

查询评论6的祖先(不包括自身)以及评论6的后代(包括自身),然后删除他们之间的关系,正确地移除评论6的祖先到评论6和它的后代之间的路径。删除 (1,6 )(1,7 )(4,6 )(4,7 )不会删除(6,6 )(6,7 )

再建立关系

对比

设计 查询子 查询树 插入 删除 引用完整性
邻接表 1 简单 困难 简单 简单
递归查询 1 简单 简单 简单 简单
枚举路径 1 简单 简单 简单 简单
嵌套集 1 困难 简单 困难 困难
闭包表 2 简单 简单 简单 简单

总结

  1. 邻接表最方便
  2. 如果支持with/connect by prior的递归查询能使邻接表更为高效
  3. 枚举路径直观展示出祖先到后代之间的路径,但同时由于它不能确保引用完整性,设计非常脆弱。存储也冗余
  4. 嵌套集是一个聪明的解决方案,但不能确保引用完整性。最好在一个查询性能要求高而对其他要求一般的场合使用
  5. 闭包表是最通用的设计,并且上述的设计中只有它能允许一个节点属于多棵树。它要求一张额外的表来存储关系,使用空间换时间的方案减少操作过程中由冗余的计算锁造成的消耗

如果简单就用邻接表但如果复杂,要查询整个树结构就用闭包表

跟着需求进行分析与设计

数据库树形结构存储方法的选择相关推荐

  1. MySQL:如何将树形结构存储在数据库中

    文章目录 问题 方案一 Adjacency List(存储父节点) 数据库存储结构 SQL示例 1.添加节点 2.查询小天的直接上司 3.查询老宋管理下的直属员工 4.查询小天的所有上司 5.查询老王 ...

  2. java 修改mysql数据库表结构_MYSQL数据库表结构优化方法详解

    摘要:这篇MySQL栏目下的"MYSQL数据库表结构优化方法详解",介绍的技术点是"mysql数据库表结构.MySQL数据库.数据库表结构.MySQL.据库表结构.数据库 ...

  3. mysql 存树 闭包表_关系型数据库树形关系存储-闭包表

    前言 在关系型数据库中,有一种逻辑关系比较难处理,这种就是树形结构.目前有很多主流的处理方案,比如说直接在业务表中存储上一级id,这样就可以用递归查询SQL的形式找到某一节点的父节点,子节点,或者兄弟 ...

  4. 数据库树形结构,EasyUI Tree 树

    通过 $.fn.tree.defaults 重写默认的 defaults. 树(tree)在网页中以树形结构显示分层数据.它向用户提供展开.折叠.拖拽.编辑和异步加载功能. 树的数据格式(Tree D ...

  5. 数据库索引结构(方法),一张图搞懂MySQL索引

    索引结构(方法) Hash索引 :对于每一行数据,存储引擎都会对所有的索引列的值计算一个哈希码存储在哈希表中.哈希索引只包含哈希值和行指针(指向实际的行数据),当有哈希冲突时,则会逐行比较符合查询条件 ...

  6. Java学习笔记<十六>(树形结构存储)

    1.树形结构的介绍 * 树形结构简介  * 树形结构是一种非线性结构,存储的是"一对多的"关系的数据元素的集合  *  * 树形结构的相关术语:  * 节点(Node):使用树结构 ...

  7. python比较两个数据库表_mysql如何比对两个数据库表结构的方法

    在开发及调试的过程中,需要比对新旧代码的差异,我们可以使用git/svn等版本控制工具进行比对.而不同版本的数据库表结构也存在差异,我们同样需要比对差异及获取更新结构的sql语句. 例如同一套代码,在 ...

  8. 数据库树形结构、多级联动的表设计

    问题:二级联动.多级联动等树形结构的数据,如何设计表格. 场景:省市县三级联动.商品的分类等. 参考:https://www.zhihu.com/question/20417447 最常用的一种方法是 ...

  9. mysql比对两个数据库表结构的方法

    在开发及调试的过程中,需要比对新旧代码的差异,我们可以使用git/svn等版本控制工具进行比对.而不同版本的数据库表结构也存在差异,我们同样需要比对差异及获取更新结构的sql语句. 例如同一套代码,在 ...

最新文章

  1. 九个Console命令,让js调试更简单
  2. SpringCloud微服务云架构构建B2B2C电子商务平台之-(八)消息总线(Spring Cloud Bus)...
  3. 5.3 计算机网络传输层之TCP协议(tcp协议特点、tcp报文段首部格式、tcp连接---三次握手、tcp连接释放---四次握手)
  4. eclipse git:Transport Error: Cannot get remote repository refs. invalid advertisement of
  5. django08: 视图与路由(旧笔记)
  6. python 中移去文件的只读属性
  7. python 科学计算设计_《Python科学计算-(第2版)》怎么样_目录_pdf在线阅读 - 课课家教育...
  8. C++---异常处理
  9. jq判断html为空,jquery如何判断是否为null?
  10. 2020年春季入学学员计算机应用基础本,2020年春季考试《计算机应用基础》在线考核试题.doc(9页)-原创力文档...
  11. 《我的第一本算法书》读书笔记
  12. 测试总结(部分)---转载
  13. 问题总结(一)使用代码调整SharePoint WebPart 视图和列表菜单
  14. python中for else举例_Python 中for...esle和while...else语法
  15. Ant Design Upload 文件上传功能
  16. bzoj2286【SDOI2011】消耗战
  17. 18124 N皇后问题
  18. 最新Discuz模板二次元动漫C风格+C风格门户版1.1 UTF8+GBK
  19. 产品经理的职责都有哪些
  20. eclipse vail_在Windows Home Server“ Vail”上安装Microsoft Security Essentials 2.0 Beta

热门文章

  1. 专业档案门类代码编码方案
  2. Win10怎么删除快速访问中最近使用文件记录
  3. Oracle Docker 镜像(制作)
  4. 单利模式的优缺点和使用场景
  5. 单利终值和现值matlab,单利终值现值和复利终值现值公式
  6. js手机号中间部分隐藏****,显示前三位和后四位
  7. 总结24个Python接单赚钱的平台,兼职月入5000+
  8. 处处吻(粤语汉字英译)
  9. 滴滴8周年:科技让出行更美好!
  10. 模拟购物数据实时流处理(4)——实时数据大屏