MySQL基于左右值编码的树形数据库表结构设计
 
在关系型数据库中设计树形的数据结构一直是一个十分考验开发者能力的,最常用的方案有主从表方案和继承关系(parent_id)方案。主从表方案的最大缺点是树形结构的深度扩展困难,一般来说都是固定的,适合深度固定的需求。继承关系方案设计和实现自然而然,非常直观和方便。缺点当然也是非常的突出:由于直接地记录了节点之间的继承关系,因此对Tree的任何 CRUD操作都将是低效的,这主要归根于频繁的“递归”操作,递归过程不断地访问数据库,每次数据库IO都会有时间开销。因此这种方案适合Tree规模相对较小的情况,我们可以借助于缓存机制来做优化,将Tree的信息载入内存进行处理,避免直接对数据库IO操作的性能开销。
理想中树形结构应该具备如下特征:检索遍历过程简单高效;节点增删改查CRUD操作高效;数据存储冗余度小、直观性强。笔者在查阅网上相关资料之后整理了一个基于左右值编码的树形结构的数据库表结构设计方案,并在MySQL数据库中实现。
首先我们记住以下这张图
图一 左右值属性结构
采用深度优先遍历给树中的每个节点分配两个值,一个左值和一个右值。节点左边的值比该节点的所有子孙节点值都要小,节点右边的值比该节点的所有子孙节点值都要大。例如:
B左边的值为2,其比Hell Mayes的所有子孙节点的值都要小(D[3,4]、E[5,10]、I[6,7]、J[8,9]、F[11,12])
B右边的值为13,其比Hell Mayes的所有子孙节点的值都要大(D[3,4]、E[5,10]、I[6,7]、J[8,9]、F[11,12])
有了这个规则整棵树的结构通过左值和右值存储了下来。
接下来我们在MySQL中建表,并实现整棵树的CURD方法
创建表结构
CREATE TABLE `tree` (
`node_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
`lft` int(11) DEFAULT NULL,
`rgt` int(11) DEFAULT NULL,
PRIMARY KEY (`node_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
INSERT INTO `tree` VALUES
(1,'A',1,20),
(2,'B',2,13),
(3,'C',14,19),
(4,'D',3,4),
(5,'E',5,10),
(6,'F',11,12),
(7,'G',15,16),
(8,'H',17,18),
(9,'I',6,7),
(10,'J',8,9);
准备工作就绪。
1)获取某个节点的子孙节点
以B为例:
SELECT* FROM Tree WHERE Lft BETWEEN 2 AND 13 ORDER BY Lft ASC
图二 B的子孙节点
某个节点到底有多少的子孙节点呢?通过该节点的左、右值我们可以将其子孙节点圈进来,则子孙总数 = (右值 – 左值– 1) / 2,以B为例,其子孙总数为:(13–2 – 1) / 2 = 5。同时,为了更为直观地展现树形结构,我们需要知道节点在树中所处的层次,通过左、右值的SQL查询即可实现。以B为例:
SELECT COUNT(*) FROM Tree WHERE Lft <= 2 AND Rgt >=13
结果为2,表明B处于该树的第二层。为了方便描述,我们可以为Tree建立一个视图,添加一个层次字段,该字段值可以写一个自定义函数来计算,函数定义如下:
定义计算指定节点所在层的函数CountLayer
CREATE DEFINER=`root`@`localhost` FUNCTION `CountLayer`(p_node_id int) RETURNS int(11)
BEGIN
declare p_result,p_lft,p_rgt int default 0;
if exists (select 1 from tree where node_id=p_node_id) then
begin
select lft, rgt into p_lft, p_rgt from tree where node_id=p_node_id;
select count(*) into p_result from tree where lft <= p_lft and rgt >= p_rgt;
end;
return p_result;
end if;
RETURN 0;
END
函数名称为CountLayer,需传入指定节点的id。此时我们可以基于刚刚定义的层计算函数来创建一个包含层次字段的试图。
定义层次试图Tree_View
CREATE
ALGORITHM = UNDEFINED
DEFINER = `root`@`localhost`
SQL SECURITY DEFINER
VIEW `tree_view` AS
SELECT
`tree`.`node_id` AS `node_id`,
`tree`.`name` AS `name`,
`tree`.`lft` AS `lft`,
`tree`.`rgt` AS `rgt`,
COUNTLAYER(`tree`.`node_id`) AS `layer`
FROM
`tree`
ORDER BY `tree`.`lft`
图三 各节点所处的层次
此时我们来创建一个存储过程,用来获取给定节点的所有子孙节点和每个节点所在的层。
获取所有子孙节点的存储过程GetChildrenNodeList
CREATE DEFINER=`root`@`localhost` PROCEDURE `GetChildrenNodeList`(in p_node_id int)
BEGIN
declare p_lft,p_rgt int default 0;
if exists (select node_id from tree where node_id=p_node_id) then
begin
select lft,rgt into p_lft,p_rgt from tree where node_id=p_node_id;
select * from Tree_View where lft between p_lft and p_rgt order by layer, lft;
end;
end if;
END
查询B的所有子孙节点
call GetChildrenNodeList(2);
可以得到
图四 B的所有子孙节点及相应的层
可以计算其子孙节点,当让也可以计算其节点。
创建获取所有父节点的存储过程GetParentNodePath
CREATE DEFINER=`root`@`localhost` PROCEDURE `GetParentNodePath`(in p_node_id int)
BEGIN
declare p_lft,p_rgt int default 0;
if exists (select node_id from tree where node_id=p_node_id) then
begin
select lft,rgt into p_lft,p_rgt from tree where node_id=p_node_id;
select * from Tree_View where lft<p_lft and rgt>p_rgt order by layer,lft asc;
end;
end if;
END
以E节点为例:
call GetParentNodePath(5);
可以得到
图五 E的所有父节点
2)在某个节点下插入一个子节点。
仔细观察图一,我们以在H下添加一个节点K为例。K节点的左值为H节点的右值,K节点的右值为其左值+1,其他所有右值大于等于K的左值的节点的右值须+2,所有左值大于等于K的左值的节点的左值须+2。图示如下:
图六 新增节点
我们将其定义为存储过程:
添加节点的存储过程AddSubNode
CREATE DEFINER=`root`@`localhost` PROCEDURE `AddSubNode`(in p_node_id int,in p_node_name varchar(50))
BEGIN
declare p_rgt int default 0;
if exists(select node_id from tree where node_id=p_node_id) then
begin
SET AUTOCOMMIT=0;
START TRANSACTION;
select rgt into p_rgt from tree where node_id=p_node_id;
update tree set rgt=rgt+2 where rgt>=p_rgt;
update tree set lft=lft+2 where lft>=p_rgt;
insert into tree(name,lft,rgt) values(p_node_name,p_rgt,p_rgt+1);
COMMIT;
end;
end if;
END
调用AddSubNode存储过程
call AddSubNode(8,'K');
图七 K节点成功插入
3)删除节点
删除节点是新增节点的逆向过程。
创建删除节点存储过程DelNode
CREATE DEFINER=`root`@`localhost` PROCEDURE `DelNode`(in p_node_id int)
BEGIN
declare p_lft,p_rgt int default 0;
if exists(select p_node_id from tree where node_id =p_node_id) then
START TRANSACTION;
select lft,rgt into p_lft,p_rgt from tree where node_id=p_node_id;
delete from tree where lft>=p_lft and rgt<=p_rgt;
update tree set lft=lft-(p_rgt - p_lft + 1) where lft > p_lft;
update tree set rgt=rgt-(p_rgt - p_lft + 1) where rgt > p_rgt;
COMMIT;
end if;
END
以删除C节点为例
call DelNode(3);
再次查询tree结果为:
图八 C及其所有的子节点都被删除
到此我们已将基于左右值编码的树形数据库表结构设计的基本原理介绍完了。当然,对于节点的操作还远不止这些,感兴趣的朋友可以自己动手实现。
诚然,这个方案也有其不足之处:节点的添加、删除及修改代价较大,将会涉及到表中多方面数据的改动。但是,在消除了递归操作的前提下实现了无限分组,而且查询条件是基于整形数字的比较,效率很高。所以,该方案比较实用与查询较多,变更不大的场景。
 

MySQL基于左右值编码的树形数据库表结构设计的更多相关文章

  1. 树形结构的数据库表Schema设计-基于左右值编码

    树形结构的数据库表Schema设计 程序设计过程中,我们常常用树形结构来表征某些数据的关联关系,如企业上下级部门.栏目结构.商品分类等等,通常而言,这些树状结构需要借助于数据库完 成持久化.然而目前的 ...

  2. 数据库表结构设计方法及原则(li)

    数据库设计的三大范式:为了建立冗余较小.结构合理的数据库,设计数据库时必须遵循一定的规则.在关系型数据库中这种规则就称为范式.范式是符合某一种设计要求的总结.要想设计一个结构合理的关系型数据库,必须满 ...

  3. ofbiz数据库表结构设计(3)- 订单ORDER

    对于订单来说,主要的表就是ORDER_HEADER和ORDER_ITEM.ORDER_HEADER就是所谓的订单头,一条记录代表一条订单. ORDER_PAYMENT_PREFERENCE是订单的支付 ...

  4. ofbiz数据库表结构设计(2)- CONTACT_MECH

    ofbiz中,party的电话.地址等联系方式设计得非常巧妙,让我们来仔细分析一下. 有一个叫做CONTACT_MECH的表,这张表我们把它称作联系方式表,一个电话号码.一个通讯地址.一个电子邮件,都 ...

  5. ofbiz数据库表结构设计(1)- PARTY

    ofbiz的精华就在于其数据结构(表结构)的设计.数据结构的通用性也决定了ofbiz几乎可以适用任何企业应用.我们首先来看看PARTY相关的表结构设计. 在ofbiz中,PARTY是个抽象概念,它可以 ...

  6. Activiti5.13数据库表结构设计

    1.结构设计 1.1.    逻辑结构设计 Activiti使用到的表都是ACT_开头的. ACT_RE_*: ’RE’表示repository(存储),RepositoryService接口所操作的 ...

  7. 【VIP视频网站项目三】项目框架搭建、项目路由配置、数据库表结构设计

    一.项目路由的设计 目前项目代码已经全部开源:项目地址:https://github.com/xiugangzhang/vip.github.io 视频网站前台页面路由设计 路由 请求方法 模板 作用 ...

  8. mysql增加远程连接用户及查看数据库表结构

    一.增加远程连接用户 1.用root权限登录数据库  2.加用户:grant all privileges on *.* to '111'@'192.168.1.%' identified by '2 ...

  9. mysql数据库连接状态,不要做修改数据库表结构的操作;数据库迁移操作;

    在开发过程中,python的flask框架使用sqlalmysql连接mysql数据库. 在程序连接数据量过程中,不要修改数据表的结构.比如在连接状态中使用下面的软件修改数据表结构,这个软件立即就会卡 ...

随机推荐

  1. Unknown system variable 'query_cache_size'] with root cause

    Unknown system variable 'query_cache_size'] with root cause 出现这个错误是因为mysql连接数据库的版本不对, mysql-connecto ...

  2. vue项目中遇到的那些事。

    前言 有好几天没更新文章了.这段实际忙着做了一个vue的项目,从 19 天前开始,到今天刚好 20 天,独立完成. 做vue项目做这个项目一方面能为工作做一些准备,一方面也精进一下技术. 技术栈:vu ...

  3. java 中类的方法

    object类,即所有类的父类, getClass() 返回对象执行时的Class实例, getClass().getName();// 返回类的名字 toString();// equals();/ ...

  4. 更改checkbox的默认样式

    最近做一个vue项目要用到checkbox要修改默认样式,选中是纯白色,不选择只有白色边框,起初以为很容易,没想到还折腾了一翻,记录一下. 几经折腾,理清input 和label的关系 最终改进版本, ...

  5. flask学习(一)

    特点: 短小精悍,可扩展性强 依赖wsgi:werkzurg werkzurg示例: from werkzeug.wrappers import Request, Response from werk ...

  6. css3写出飘雪花特效

    大冬天的,飘雪花的特效,你可能要用上了吧.通常情况下用jQuery写飘雪花的特效,但用css3写,其实特别简单,新手一看就懂,那就告别jQuery,用css3轻松搞定飘雪花特效吧! 点击查看特效演示 ...

  7. [insight] debug

    python: 1. print理解流程 print('xy1') print('xy2') 可以更好地跟踪函数的执行流程,分析代码 2. 用python库 import pdb; pdb.set_t ...

  8. pwnable.tw hacknote

    产生漏洞的原因是free后chunk未置零 unsigned int sub_80487D4() { int index; // [esp+4h] [ebp-14h] char buf; // [es ...

  9. JUC--闭锁 CountDownLatch

    CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,允许一个或者多个线程一直等待. 闭锁可以延迟线程的进度直到其到达终止状态,可以确保某些活动知道其他活动都完成才继续 ...

  10. 04mycat数据切分

    自定义切分文件 [root@mycat mycat]# cat conf/customer-hash-int.txt 101=0 102=0 103=0 104=1 105=1 106=1 Rule. ...