树形数据在关系数据库的存储
2014-03-25 15:34
148 查看
树形数据在关系数据库中的存储同对象一样,都会遇到一个"阻抗不匹配"的问题。如何设计一个表结构,才能较好的满足需求呢?
事实上,有很多解决方案,但是没有哪一种是放之四海而皆准的。我个人认为解决方案的选择,必须依赖于需求背景。抛弃需求背景而就技术泛泛而谈,就如同孔乙己对回字不同写法的孜孜追求,满身酸腐之气。
凡事有得就有失,十全十美的方案是不存的,合适的就是最好的。
下面就集中常见的方案做一下比对,然后详细分析一下第四种方案。
方案一:parent_id
Pros:
非常容易实现
很方便的将子树移动到另外一个节点
添加节点非常简单
Cons:
检索整棵树需要使用递归,非常耗费时间
查找指定节点的所有父(子)节点同样要使用递归,非常耗费时间
方案二: Path栏位
表添加Path栏位,每个节点记录从根节点到自身的一个路径。
Pros:
很容易查找所有父子节点
很容易检索整棵树
添加节点很简单
Cons:
移动子树比较麻烦,会导致大量数据更新
取决于path的存储方式,可能需要对path进行解析
方案三:Path表
建立一个表,记录节点和其所有父节点的关联关系。
Pros:
很容易查找所有父子节点
很容易检索整棵树
Cons:
添加节点很麻烦,需要同时产生很多关联关系
移动子树导致大量数据操作
数据量可能很庞大
方案四: 预排序遍历树算法(modified preorder tree traversal algorithm)
Pros:
很容易查找所有父子节点
很容易检索整棵树
直接使用SQL就可以得到相关的数据
存储方式决定了子节点都是有序存储的
Cons:
新增,更新,移动都很复杂,每次都会变更非常多的数据
第四种方案的算法的图例如下,这个图例比较难以理解,我另外做了个图例,以矩形嵌套来描述它。如下面的图一图二:
(图一)
(图二)
图二中的节点以矩形嵌套矩形的方式来描述父子关系,每个框代表一个节点,左右边框各记录一个值。值的维护从左到右依次递增。
这个图例很容易看出如何使用一个节点的左右边界值来查找其子节点。
我们转成二维表,看看数据库的存储形式:
下面我们来看看树上的典型操作如何实现:
1. 检索树
给定一个节点,查找该节点及其子节点的sql:
2. 查找所有父节点
所有父节点的特征是左边值小于当前节点的左值,右边值大于当前节点的右值
3. 查找路径 Food > Cherry
改变lft的排序方式,即可实现从父节点>子节点,或者子节点到父节点
4. 计算子节点的数目
每个子节点占2个数据,计算公式如下。判断当前节点是否叶子节点,或者有多少个子节点,可以根据下面的公式
descendants = (right - left - 1) / 2
5. 新增子节点
以在Food>Fruit>Yellow 右边再增加一个新的兄弟节点Black为例子
1) 父节点的右边值为11. 新节点的左右值应该为 11, 12
2) 变更所有的受影响的节点,给新节点腾出空位子。
所有左节点>=11的,都增加2
6. 删除子节点
以删除Food>Fruit>Red 节点为例子
1) 删除子节点及其下面所有节点
2) 变更所有的受影响的节点.
所有左节点大于6的减去4 (rgt - lft + 1)
所有右节点大于6的减去4
7. 移动子节点
以将Food>Fruit>Yellow 移动到 Food>Meat>Pork下为例子:
要移动的节点是(7,11),目标节点是(15,16)
1) 以目标节点的Pork为参考,变更要移动节点的值
2) 原节点所在位置以被删除看待
所有左节点大于11的减去5 (rgt - lft + 1)
所有右节点大于11的减去5
可以看出这种方法的最大优势是提升了读的性能,但是牺牲了写的性能,而且写的时候必须锁表。
以下资料供参考:
Storing Hierarchical Data in a Database
http://media.pragprog.com/titles/bksqla/trees.pdf
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
https://communities.bmc.com/docs/DOC-9902
http://fungus.teststation.com/~jon/treehandling/TreeHandling.htm (Path Table)
事实上,有很多解决方案,但是没有哪一种是放之四海而皆准的。我个人认为解决方案的选择,必须依赖于需求背景。抛弃需求背景而就技术泛泛而谈,就如同孔乙己对回字不同写法的孜孜追求,满身酸腐之气。
凡事有得就有失,十全十美的方案是不存的,合适的就是最好的。
下面就集中常见的方案做一下比对,然后详细分析一下第四种方案。
方案一:parent_id
Pros:
非常容易实现
很方便的将子树移动到另外一个节点
添加节点非常简单
Cons:
检索整棵树需要使用递归,非常耗费时间
查找指定节点的所有父(子)节点同样要使用递归,非常耗费时间
方案二: Path栏位
表添加Path栏位,每个节点记录从根节点到自身的一个路径。
Pros:
很容易查找所有父子节点
很容易检索整棵树
添加节点很简单
Cons:
移动子树比较麻烦,会导致大量数据更新
取决于path的存储方式,可能需要对path进行解析
方案三:Path表
建立一个表,记录节点和其所有父节点的关联关系。
Pros:
很容易查找所有父子节点
很容易检索整棵树
Cons:
添加节点很麻烦,需要同时产生很多关联关系
移动子树导致大量数据操作
数据量可能很庞大
方案四: 预排序遍历树算法(modified preorder tree traversal algorithm)
Pros:
很容易查找所有父子节点
很容易检索整棵树
直接使用SQL就可以得到相关的数据
存储方式决定了子节点都是有序存储的
Cons:
新增,更新,移动都很复杂,每次都会变更非常多的数据
第四种方案的算法的图例如下,这个图例比较难以理解,我另外做了个图例,以矩形嵌套来描述它。如下面的图一图二:
(图一)
(图二)
图二中的节点以矩形嵌套矩形的方式来描述父子关系,每个框代表一个节点,左右边框各记录一个值。值的维护从左到右依次递增。
这个图例很容易看出如何使用一个节点的左右边界值来查找其子节点。
我们转成二维表,看看数据库的存储形式:
Parent | Title | lft | rgt |
Food | 1 | 18 | |
Food | Fruit | 2 | 11 |
Fruit | Red | 3 | 6 |
Red | Cherry | 4 | 5 |
Fruit | Yellow | 7 | 10 |
Yellow | Banana | 8 | 9 |
Food | Meat | 12 | 17 |
Meat | Beef | 13 | 14 |
Meat | Pork | 15 | 16 |
1. 检索树
给定一个节点,查找该节点及其子节点的sql:
SELECT * FROM tree WHERE lft BETWEEN 2 AND 11 ORDER BY lft ASC;
2. 查找所有父节点
所有父节点的特征是左边值小于当前节点的左值,右边值大于当前节点的右值
SELECT title FROM tree WHERE lft < 4 AND rgt > 5 ORDER BY lft ASC;
3. 查找路径 Food > Cherry
select * from tree where lft between 1 and 4 and rgt between 5 and 18 order by lft
改变lft的排序方式,即可实现从父节点>子节点,或者子节点到父节点
4. 计算子节点的数目
每个子节点占2个数据,计算公式如下。判断当前节点是否叶子节点,或者有多少个子节点,可以根据下面的公式
descendants = (right - left - 1) / 2
5. 新增子节点
以在Food>Fruit>Yellow 右边再增加一个新的兄弟节点Black为例子
1) 父节点的右边值为11. 新节点的左右值应该为 11, 12
2) 变更所有的受影响的节点,给新节点腾出空位子。
所有左节点>=11的,都增加2
update tree set lft = lft + 2 where lft>=11所有右节点>=11的,都增加2
update tree set rgt = rgt + 2 where rgt>=113) 新节点放到空位上,左右边值分别为11,12
INSERT INTO tree SET lft=11, rgt=12, title='Black'
6. 删除子节点
以删除Food>Fruit>Red 节点为例子
1) 删除子节点及其下面所有节点
delete from tree where lft >= 3 and rgt <= 6
2) 变更所有的受影响的节点.
所有左节点大于6的减去4 (rgt - lft + 1)
update tree set lft = lft - 4 where lft > 6
所有右节点大于6的减去4
update tree set rgt = rgt - 4 where rgt> 6
7. 移动子节点
以将Food>Fruit>Yellow 移动到 Food>Meat>Pork下为例子:
要移动的节点是(7,11),目标节点是(15,16)
1) 以目标节点的Pork为参考,变更要移动节点的值
update tree set lft = lft + 9, rht=rgt + 9 where lft >=7 and rgt<=11
2) 原节点所在位置以被删除看待
所有左节点大于11的减去5 (rgt - lft + 1)
update tree set lft = lft - 5 where lft > 11
所有右节点大于11的减去5
update tree set rgt = rgt - 5 where rgt> 11
可以看出这种方法的最大优势是提升了读的性能,但是牺牲了写的性能,而且写的时候必须锁表。
以下资料供参考:
Storing Hierarchical Data in a Database
http://media.pragprog.com/titles/bksqla/trees.pdf
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
https://communities.bmc.com/docs/DOC-9902
http://fungus.teststation.com/~jon/treehandling/TreeHandling.htm (Path Table)
相关文章推荐
- 基于树型结构数据的关系数据库存储与网页显示的研究 推荐
- [ mongoDB ] - 文档型数据库设计模式-如何存储树形数据 [转]
- 关系数据库中存储树形结构
- Atitit.各种 数据类型 ( 树形结构,表形数据 ) 的结构与存储数据库 attilax 总结
- [ mongoDB ] - 文档型数据库设计模式-如何存储树形数据
- Atitit.各种 数据类型 ( 树形结构,表形数据 ) 的结构与存储数据库 attilax 总结
- 将选定的 OmniFind 分析结果存储到关系数据库中以便进行报告和数据挖掘
- Json树形结构数据转Java对象并存储到数据库的实现-超简单的JSON复杂数据处理 .
- Hive简介、什么是Hive、为什么使用Hive、Hive的特点、Hive架构图、Hive基本组成、Hive与Hadoop的关系、Hive与传统数据库对比、Hive数据存储(来自学习资料)
- 树形结构的数据存储和数据库表设计
- 从数据库中读出树形数据,并快速构建树型类关系
- 关系数据库已经统治数据存储30 多年
- 文档型数据库设计模式-如何存储树形数据
- 数据库存储树形结构的数据
- 基于关系数据库系统链式存储的树型结构数据,求某结点下的子树所有结点算法(t-sql语言实现)
- 关系数据库已经统治数据存储30 多年
- (原创)基于关系数据库系统链式存储的树型结构数据,求某结点下的子树所有结点算法(t-sql语言实现)
- 树形结构的数据存储与数据库表设计
- 文档型数据库设计模式-如何存储树形数据
- 本文是笔者根据数据库编程经验,利用C++语言的模板、继承、授权、多态等面向对象特性,借鉴命令模式,实现了对象在关系数据中的存储,降低应用系统与数据库之间的耦合,提高开发效率。