`
langgufu
  • 浏览: 2287986 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

如何在数据库中存储一棵树

阅读更多

树形结构的数据在项目开发中比较常见,比如比较典型的是论坛主题留言。

每一个主题(节点)可以有n个留言(子节点)。这些留言又可以有自己的留言。因此这种结构就是一颗树。本文讨论的是数据库中如何存储这种树形结构。

假设有如下一棵树:

无标题

方法一

注意:本例中的数据库是SQLite,因此SQL语句只对SQLite有效,其他数据库可以参考该写法。

要存储于数据库中,最简单直接的方法,就是存储每个元素的父节点ID。

暂且把这种方法命名依赖父节点法,因此表结构设计如下:

598F8C3BAEC249C7B7C21FCAE42C097F

存储的数据如下格式:

D91E5117473F4F75B42E8542953BE78C

这种结构下,如果查询某一个节点的直接子节点,十分容易,比如要查询D节点的子节点。

1
select * from tree1 where parentid=4

如果要插入某个节点,比如在D节点下,再次插入一个M节点。

只需要如下SQL:

1
INSERT INTO tree1 (value,parentid) VALUES('M',4);

这种结构在查找某个节点的所有子节点,就稍显复杂,无论是SELECT还是DELETE都可能涉及到获取所有子节点的问题。比如要删除一个节点并且该节点的子节点也要全部删除,那么首先要获得所有子节点的ID,因为子节点并不只是直接子节点,还可能包含子节点的子节点。比如删除D节点及其子节点,必须先查出D节点下的所有子节点,然后再做删除,SQL如下:

1
2
3
4
select nodeid from tree1 where parentid=4 --返回8,9
select nodeid from tree1 where parentid in (8,9) --返回10,11,12
select nodeid from tree1 where parentid in (10,11,12) --返回空
delete from tree1 where nodeid in (4,8,9,10,11,12)

如果是只删除D节点,对于其它节点不做删除而是做提升,那么必须先修改子节点的parentid,然后才能删除D节点。

正如上面演示的,对于这种依赖父节点法,最大的缺点就是无法直接获得某个节点的所有子节点。因此如果要select所有的子节点,需要繁琐的步骤,这不利于做聚合操作。

对于某些数据库产品,支持递归查询语句的,比如微软的SQL Server,可以使用CTE技术实现递归查询。比如,要查询D节点的所有子节点。只需要如下语句:

1
2
3
4
5
6
WITH tmp AS(
SELECT * FROM Tree1 WHERE nodeid = 4
UNION ALL
SELECT a.* FROM Tree1 AS a,tmp AS b WHERE a.parentid = b. nodeid
)
SELECT * FROM tmp

但是对于那些不支持递归查询的数据库来说,实现起来就比较复杂了。

 

方法二

还有一种比较土的方法,就是存储路径。暂且命名为路径枚举法。

这种方法,将存储根结点到每个节点的路径。

55778B9842DC47279FFCFF48B54ABDA1

这种数据结构,可以一眼就看出子节点的深度。

如果要查询某个节点下的子节点,只需要根据path的路径去匹配,比如要查询D节点下的所有子节点。

1
select * from tree2 where path like '%/4/%'

或者出于效率考虑,直接写成

1
select * from tree2 where path like '1/4/%'

214EF7DB11684064ABB9C4FCBDDD5CD4

如果要做聚合操作,也很容易,比如查询D节点下一共有多少个节点。

select count(*) from tree2 where path like '1/4/%';

要插入一个节点,则稍微麻烦点。要插入自己,然后查出父节点的Path,并且把自己生成的ID更新到path中去。比如,要在L节点后面插入M节点。

首先插入自己M,然后得到一个nodeid比如nodeid=13,然后M要插入到L后面,因此,查出L的path为1/4/8/12/,因此update M的path为1/4/8/12/13

1
2
3
4
5
update tree2 set
path=(select path from tree2 where nodeid=12) --此处开始拼接
||last_insert_rowid()||'/'
where
nodeid= last_insert_rowid();

这种方法有一个明显的缺点就是path字段的长度是有限的,这意味着,不能无限制的增加节点深度。因此这种方法适用于存储小型的树结构。

方法三

下面介绍一种方法,称之为闭包表。

该方法记录了树中所有节点的关系,不仅仅只是直接父子关系,它需要使用2张表,除了节点表本身之外,还需要使用1张表来存储节祖先点和后代节点之间的关系(同时增加一行节点指向自身),并且根据需要,可以增加一个字段,表示深度。因此这种方法数据量很多。设计的表结构如下:

Tree3表:

E1D5EEEE05EF4188ADE17192C9B95ECC

NodeRelation表:

C3E90EA4EEBE490D87035F98DFC39EA2

如例子中的树,插入的数据如下:

Tree3表的数据

20ADFF42DB6E45CC9CA0C287DA49C5B5

NodeRelation表的数据

9F3B8EC76E0B4D67830FF29B6F6EEC4E

可以看到,NodeRelation表的数据量很多。但是查询非常方便。比如,要查询D节点的子元素

只需要

1
select * from NodeRelation where ancestor=4;

要查询节点D的直接子节点,则加上depth=1

1
select * from NodeRelation where ancestor=4 and depth=1;

要查询节点J的所有父节点,SQL:

1
select * from NodeRelation where descendant=10;

如果是插入一个新的节点,比如在L节点后添加子节点M,则插入的节点除了M自身外,还有对应的节点关系。即还有哪些节点和新插入的M节点有后代关系。这个其实很简单,只要和L节点有后代关系的,和M节点必定会有后代关系,并且和L节点深度为X的和M节点的深度必定为X+1。因此,在插入M节点后,找出L节点为后代的那些节点作为和M节点之间有后代关系,插入到数据表。

1
2
3
4
5
6
7
INSERT INTO tree3 (value) VALUES('M');--插入节点
INSERT INTO  NodeRelation(ancestor,descendant,depth)
select n.ancestor,last_insert_rowid(),n.depth+1--此处深度+1作为和M节点的深度
from NodeRelation n
where n.descendant=12
Union ALL
select  last_insert_rowid() ,last_insert_rowid(),0 --加上自身

在某些并不需要使用深度的情况下,甚至可以不需要depth字段。

如果要删除某个节点也很容易,比如,要删除节点D,这种情况下,除了删除tree3表中的D节点外,还需要删除NodeRelation表中的关系。

首先以D节点为后代的关系要删除,同时以D节点的后代为后代的这些关系也要删除:

1
2
delete from NodeRelation where descendant in
(select descendant from NodeRelation where ancestor=4 );--查询以D节点为祖先的那些节点,即D节点的后代。

这种删除方法,虽然彻底,但是它也删除了D节点和它原本的子节点的关系。

如果只是想割裂D节点和A节点的关系,而对于它原有的子节点的关系予以保留,则需要加入限定条件。

限制要删除的关系的祖先不以D为祖先,即如果这个关系以D为祖先的,则不用删除。因此把上面的SQL加上条件。

1
2
3
delete from NodeRelation where descendant in
(select descendant from NodeRelation where ancestor=4 );--查询以D节点为祖先的那些节点,即D节点的后代。
and ancestor not in (select descendant from NodeRelation  where ancestor =4 )

上面的SQL用文字描述就是,查询出D节点的后代,如果一个关系的祖先不属于D节点的后代,并且这个关系的后代属于D节点的后代,就删除它。

这样的删除,保留了D节点自身子节点的关系,如上面的例子,实际上删除的节点关系为:

569AD87B6E7B4F428D3521B550F9D0FF

如果要删除节点H,则为

8579EB3DB87C4175B5DAAEAA9E182395

总结:

上面主要讲了3种方式,各有优点缺点。可以根据实际需要,选择合适的数据模型。

 

分享到:
评论

相关推荐

    数据库存储树结构ClosureTableCateogryStore-master.zip

    参考本人csdn上的文章,配合看代码,会简单些。这里给出了用数据库怎么存储一棵树。采用java实现。一般来说对于树结构,使用结构化数据库存储是一个麻烦的事情。

    全面了解数据库设计中分类算法

    在网站建设中,分类算法的应用非常的普遍。...们知道:分类的数据结构实际上是一棵树。在《数据结构》课程中,大家可能学过Tree的算法。由于在网站建设中我们大量使用数据库,所以我们将从Tree在数据库中的存储谈起。

    数据库学习基础之名词解释

     (2)强大的数据处理功能,在一个工作组级别的网络环境中,使用Access开发的多用户数据库管理系统具有传统的XBASE(DBASE、FoxBASE的统称)数据库系统所无法实现的客户服务器(Cient/Server)结构和相应的数据库安全...

    动态可扩展的数据库设计.docx

    的建 筑或者物体比如一棵树一个石凳,存储的信息明显和一座教学楼不 同,需要按照类型的不同,设计多个不同的数据表分别存储不同类 型的物体信息。 动态可扩展的数据库设计全文共3页,当前为第2页。 动态可扩展的...

    tree-talk:在默克尔森林中的一棵树,您可以在那里闲逛并交谈

    树谈默克尔森林中的一棵树,您可以在那里闲逛并交谈这是什么? Tree-Talk是一个讨论的论坛,主要是pubsub部分。 将线程/帖子发送给其他用户没有涉及服务器,但是有一个信令服务器可以帮助同级发现彼此。 Tree-Talks...

    scallionDB:内存树数据库

    大葱数据库内存中的 JSON 树数据库。 要求: Python 2.7+ pyzmq 14.4+ ScallionDB 旨在从 JSON 文件中实现无模式和可移植性,即 ScallionDB 可以从磁盘... SAVE 请求将在光盘上保存一棵树在配置文件中配置 saveLimit

    数据库软件商店管理课设(含源码)

    2.该程序运行时,会自动创建一棵二叉排序树,树中每个结点都对应一个软件包,其健值包含软件包的名字和它的版本,结点中的另一个域包含该软件包在software文件中的位置; 3.只能通过二叉排序树访问software中存储的...

    二叉树的应用:二叉排序树BST和平衡二叉树AVL的构造 课设作业 完整代码

    树型结构是一类重要的非线性数据结构。其中以树和二叉树最为常用,直观看来,树是以分支关系定义的层次结构。...用二叉链表作存储结构,生成一棵二叉排序树T。 对二叉排序树T作中序遍历,输出结果。

    数据库作业4-物理存储与查询优化1

    一、假设我们利用可扩展 hash 方法(散列函数 h(k)是一个 b(足够大)位二进制 二、已知一棵 B+树,如图 1 所示 三、用下面的关键码值集合建立一个

    数据库 邮编地址查询系统

    每个省节点指向一棵对应的地区子树,这样在查询邮编时节省查询的时间。本系统采用二进制文件方式来存储查询树信息,其中provence文件存储省节点信息,Area文件存储地区节点信息。 本系统不仅提供了邮编地址查询功能...

    计算机二级C语言考试题预测

    (13) 设一棵完全二叉树共有699个结点,则在该二叉树中的叶子结点数为(B) 注:利用公式n=n0+n1+n2、n0=n2+1和完全二叉数的特点可求出 A. 349 B. 350 C. 255 D. 351 (14) 结构化程序设计主要强调的是(B) A.程序的规模 ...

    SATree:SQLAlchemy的TreeModel

    SATree可以在一张数据库表中存储多棵树,并可以方便地进行树的增加、删除、移动、输出等。树的一个节点在存储为一条记录,表现为SQLALchemy一个混合了TreeMixin的Model实例。安装通过pip进行安装pip install satree...

    二级C语言公共基础知识

    (11) 设一棵完全二*树共有500个结点,则在该二*树中有______个叶子结点。 答:250 (12) 在最坏情况下,冒泡排序的时间复杂度为______。 答:n(n-1)/2#n*(n-1)/2#O(n(n-1)/2)#O(n*(n-1)/2) (13) 面向对象的程序设计...

    数据库 200309

    1. 索引是什么?是如何实现的? 在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,...B 树,每个节点都存储 key 和 data,所有节点组成这棵树,并且叶子节点指针为

    计算机二级公共基础知识

    定义一棵树的根结点所在的层次为1,其他结点所在的层次等于它的父结点所在的层次加1。树的最大层次称为树的深度。例如,在图1-1中,根结点A在第1层,结点B,C在第2层,结点D,E,F在第3层。该树的深度为3。 子树 在...

    oracle学习文档 笔记 全面 深刻 详细 通俗易懂 doc word格式 清晰 连接字符串

    db2 IBM DB2在企业级的应用最为广泛, 在全球的500家最大的企业中,几乎85%以上用DB2数据库服务器。收费 大型企业 Access 微软 Access是一种桌面数据库,只适合数据量少的应用,在处理少量 数据和单机访问的数据库时...

    natural_rejection:生命之树Web项目中活着和已灭绝物种的数据可视化

    数据不包含在此存储库中。 要运行可视化,需要在data /目录中找到一个名为“ tree_of_life_complete.xml”的文件。 有关数据库以及如何访问数据库的信息,可以在这里找到: 或者,更改natural_rejection.pde中的...

    数据结构面试题 java面试题

    16.设一棵二叉树中有3个叶子结点,有8个度为1的结点,则该二叉树中总的结点数为(13) 17.已知二叉树后序遍历序列是dabec,中序遍历序列是debac,它的前序遍历序列是(cedba) 18.已知一棵二叉树前序遍历和中序遍历...

    浅谈MySQL的B树索引与索引优化

    MySQL的MyISAM、InnoDB引擎默认均使用B+树索引(查询时都显示为“BTREE”),本文讨论两个问题:为什么MySQL等主流...假设索引存储在内存中。也就是说,每在物理盘上保存2G的数据,就要占用200MB的内存,索引:数据

    simcity:SimCity BuildIt助手

    这些对象相互依赖,从而创建了一棵树。 我发现这是图形数据库的好用例,因此决定将游戏数据导入Neo4j。 您将在此存储库中找到将数据库中的游戏数据导入所需的脚本,并在一些查询下找到了使用这些数据的脚本。 您...

Global site tag (gtag.js) - Google Analytics