树型结构数据存储实践

很多业务场景会遇到树形结构的数据,如公司的人员职级树、行政区划树等。
使用类似MySQL的数据库进行存储,需要将树形结构(二维)存储到行格式(一维)的db中。

本文介绍了树型结构数据存储的三种方式:Adjacency Table , Nested Set , Bridge Table (Closure Table)。

以下方法均基于场景:
设想一个职员团队树,节点中为职工工号id和职工名称,节点1指向2表示职工1属于职工2的团队:
在这里插入图片描述

我们有如下的操作:

  • 新增职工节点
  • 删除职工节点
  • 查询该职工节点下属的-1职工节点
  • 查询该职工节点的所有下属职工节点
  • 查询该职工节点的+1领导节点
  • 查询该职工节点的所有领导节点

Adjacency Table

最简单的,我们构建一个邻接表,表中记录了当前职工id及其领导职工id(pid),数据组织结构如下:

id 职工idname 职工姓名pid 职工+1领导的职工id
101Anull
102Bnull
103C101
104D101

则我们可以生成如下的sql建表语句:

CREATE TABLE `employee_adjacency_table` (`id` bigint NOT NULL COMMENT '职工id',`name` varchar(64) NOT NULL COMMENT '职工姓名',`pid` bigint COMMENT '+1领导的职工id',`deleted` tinyint DEFAULT 0 COMMENT '软删标记',PRIMARY KEY (`id`),KEY `idx_pid` (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

新增职工节点

在节点108下插入叶子节点113:

INSERT INTO employee_adjacency_table (id,name,pid) VALUE (113,'M',108);

在105节点下插入非叶子节点113:

INSERT INTO employee_adjacency_table VALUE (113,'M',105);
-- 将105的-1子节点移植到113下
UPDATE employee_adjacency_table SET pid = 108 WHERE pid = 105;

删除职工节点

删除叶子节点111:

UPDATE employee_adjacency_table SET deleted = 1 where id = 111;

删除非叶子节点107,其叶子节点移植到107的+1领导节点下:

UPDATE employee_adjacency_table SET deleted = 1 where id = 107;
-- 查出107的领导节点,即105
SELECT pid FROM employee_adjacency_table WHERE id = 107 and deleted = 0 FOR UPDATE;
UPDATE employee_adjacency_table SET pid = 105 WHERE pid = 107;

查询该职工节点下属的-1职工节点

查询节点105下的-1子节点

SELECT * FROM employee_adjacency_table WHERE pid = 105 and deleted = 0

查询该职工节点的所有下属职工节点

查询节点105下的所有下属节点
需要每次查询一层数据,每次将查处的id作为pid查询条件继续查下一层,直到结果为空。

查询该职工节点的+1领导节点

查询节点109的+1领导节点

-- 查到109的+1领导节点,即105
SELECT pid FROM employee_adjacency_table WHERE id = 109 and deleted = 0;
SELECT * FROM  employee_adjacency_table WHERE id = 105 and deleted = 0;

查询该职工节点的所有领导节点

查询节点112的所有领导节点
需要每次查询一层数据,每次将查处的pid作为id查询条件继续查上一层,直到pid为null。

优缺点及适用场景

优点:结构简单,节点变更简单
缺点:查询多层级节点效率低

一般树形数据会在服务启动时从数据库导入全量数据到缓存中。适合节点数量不大,变更少,变更实时性要求低的场景

Nested Set

相比与Adjacency Table 使用pid记录父级节点, Nested Set使用一对值(left & right)刻画树的父子关系。

以102为root的树为例,将其转化为Nested Set形式,每个节点转化为一个数值范围 [left, right],如下图所示:

在这里插入图片描述

层级关系由数据范围的包含关系表示。比如工号102的职工的范围是 [1,12], 其下属职工105的范围是 [2,9],注意到叶子节点的left和right差值都是1。

则我们可以生成如下的sql建表语句:

CREATE TABLE employee_nested_set (`id` bigint NOT NULL COMMENT '职工id',`name` varchar(64) NOT NULL COMMENT '职工姓名',`left` int NOT NULL,`right` int NOT NULL,`deleted` tinyint DEFAULT 0 COMMENT '软删标记',PRIMARY KEY (`id`),KEY `idx_left` (`left`),KEY `idx_right` (`right`)
);

新增职工节点

我们要在职工110下面新增一个职工113,由于113是叶子结点,所以其left和right差值为1,且值必须在110的数值范围内,这样
只能将110的范围扩大,随之而来的是其右边值的统一扩大。

则新增的sql语句为(不能并发更新):

-- 找到节点110的左右值,即[7,8]
SELECT left,right FROM employee_nested_set where id = 110 and deleted = 0 FOR UPDATE;-- 更新右侧left和right值
UPDATE employee_nested_set SET left = left + 2 WHERE left > 8  and deleted = 0;
UPDATE employee_nested_set SET right = right + 2 WHERE right >= 8  and deleted = 0;-- 插入值范围
INSERT INTO employee_nested_set (id,name,left,right) VALUE (113, "M", 8 , 9);

我们要在职工110下面新增一个职工113,由于113是叶子结点,所以其left和right差值为1,且值必须在110的数值范围内,这样
只能将110的范围扩大,随之而来的是其右边值的统一扩大。

如果要在105和109之间插入新节点114呢?

-- 找到109的左右值,即 [3,6]
SELECT left,right FROM employee_nested_set WHERE id = 109 AND deleted = 0;UPDATE employee_nested_set SET left = left + 1 , right = right + 1 WHERE left >= 3 and deleted = 0;
UPDATE employee_nested_set SET left = left + 1 , right = right + 1 WHERE left >= 6+1+1 and deleted = 0;
INSERT INTO employee_nested_set (id,name,left,right) VALUE (114,'N',3,8);

删除职工节点

比如删除节点109,109的从属节点继承到109的领导节点下:

-- 找到109的左右值,即 [3,6]
SELECT left,right FROM employee_nested_set WHERE id = 109 AND deleted = 1;UPDATE employee_nested_set SET left = left - 1,right = right - 1 WHERE left BETWEEN 3 AND 6;
UPDATE employee_nested_set SET left = left - 1,right = right - 1 WHERE left > 7;

查询该职工节点下属的-1职工节点

很麻烦,比如找到105的-1职工节点:

SELECT node.id, (COUNT(parent.id) - (sub_tree.depth + 1)) AS depth
FROM employee_nested_set AS node,employee_nested_set AS parent,employee_nested_set AS sub_parent,(SELECT node.id, (COUNT(parent.id) - 1) AS depthFROM employee_nested_set AS node,employee_nested_set AS parentWHERE node.left BETWEEN parent.left AND parent.rightAND node.id = 105GROUP BY node.nameORDER BY node.left)AS sub_tree
WHERE node.left BETWEEN parent.left AND parent.rightAND node.left BETWEEN sub_parent.left AND sub_parent.rightAND sub_parent.id = sub_tree.id
GROUP BY node.id
HAVING depth <= 1
ORDER BY node.left;

查询该职工节点的所有下属职工节点

很方便,比如找职工105下所有的职工id:

-- 找到105的left和right,即[2,9]
SELECT left,right FROM employee_nested_set WHERE id = 105 and deleted = 0;
-- 找到2和9之间的left的节点
SELECT id FROM employee_nested_set WHERE left BETWEEN 2 AND 9 and deleted = 0;

查询该职工节点的+1领导节点

找到职工105的+1领导节点,即102。

比较trick的写法:

SELECT parent.id 
FROM employee_nested_set AS node, employee_nested_set AS parent 
WHERE parent.left < node.left 
AND parent.right > node.right 
AND node.id =105 
ORDER BY ( parent.right - parent.left ) ASC LIMIT 1;

查询该职工节点的所有领导节点

也很方便,比如找到职工110所有的领导节点:

-- 找到节点110的left和right,即[7,8]
SELECT left,right FROM employee_nested_set WEHERE id = 110 and deleted = 0;
-- 找到left<7 && right>8的节点即为其领导节点
SELECT id FROM employee_nested_set WHERE left < 7 and right > 8 and deleted = 0;

优缺点及适用场景

优点:适合查询所有下属节点的场景
缺点:数据从属关系不直观,变更操作复杂,时间复杂度高,且其他查询场景的sql语句复杂

适用于查询所有下属节点且节点变更频率低的场景,可以配合邻接表,邻接表作为变更入口,而Nested Set根据邻接表构造而成,查询所有下属节点的场景走NestSet

Bridge Table (Closure Table)

闭包表使用两张表记录数据,一张记录节点信息,一张记录ancestor节点到descendant节点之间的距离。

在这里插入图片描述

-- 节点信息表
CREATE TABLE `employee_node` (`id` bigint NOT NULL,`name` int NOT NULL,`deleted` tinyint NOT NULL DEFAULT '0',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 -- 节点与下属节点之间的距离表
CREATE TABLE `employee_node_distance` (`id` bigint NOT NULL,`ancestor_id` bigint NOT NULL,`descendant_id` bigint NOT NULL,`distance` int NOT NULL,`deleted` tinyint NOT NULL DEFAULT '0',PRIMARY KEY (`id`),KEY `idx_anc_dist` (`ancestor_id`,`distance`),KEY `idx_desc_dist` (`descendant_id`,`distance`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

新增职工节点

在节点110下插入叶子节点113:

INSERT INTO employee_node (id,name) VALUE (113,'M');-- 查处descendant_id为110的所有ancestor_id和到110的距离
SELECT ancestor_id , distance FROM employee_node_distance
WHERE descendant_id = 110;-- 根据上面查处的id和distance插入113的数据
INSERT INTO employee_node_distance (ancestor_id, descendant_id, distance) 
VALUES (113,113,0),(ancestorIdOf110,113,distanceOf110+1);

在105节点和109节点间插入非叶子节点113:

INSERT INTO employee_node (id,name) VALUE (113,'M');-- 查出105的所有领导节点-- 插入113和领导节点的距离-- 查处所有109的下属节点-- 插入113和下属节点的距离-- 根据109及其下属节点到他们领导节点的距离(+1)

删除职工节点

删除节点105:


查询该职工节点下属的-1职工节点

很方便,比如查询105的-1职工节点:

SELECT descendant_id FROM employee_node_distance
WHERE ancestor_id = 105 and distance = 1 and deleted = 0;

查询该职工节点的所有下属职工节点

很方便,比如查询105下所有下属节点:

SELECT descendant_id FROM employee_node_distance 
WHERE ancestor_id = 105 and descendant_id != 105 and deleted = 0;

查询该职工节点的+1领导节点

很方便,比如查询109的+1领导节点,即105:

SELECT ancestor_id FROM employee_node_distance 
WHERE descendant_id = 109 and distance = 1 and deleted = 0;

查询该职工节点的所有领导节点

很方便,比如查询112的所有领导节点

SELECT ancestor_id FROM employee_node_distance 
WHERE descendant_id = 102 and ancestor_id != 102 and deleted = 0;

优缺点及适用场景

优点:满足各种场景查询,sql语句简单好理解
缺点:占用表空间大,空间复杂度O(N^2) N为节点个数,子节点变动需要更新所有领导节点数据

适用于节点数量少,但查询复杂的场景

参考

  • What are the options for storing hierarchical data in a relational database?
  • Managing Hierarchical Data in MySQL
  • Convert an Adjacency List to Nested Sets

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/867304.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java视频点播网站

作者介绍&#xff1a;计算机专业研究生&#xff0c;现企业打工人&#xff0c;从事Java全栈开发 主要内容&#xff1a;技术学习笔记、Java实战项目、项目问题解决记录、AI、简历模板、简历指导、技术交流、论文交流&#xff08;SCI论文两篇&#xff09; 上点关注下点赞 生活越过…

[FreeRTOS 基础知识] 事件组 概念

文章目录 事件组 定义事件组 基本原理 事件组 定义 在实时操作系统&#xff08;RTOS&#xff09;中&#xff0c;事件组是一种用于任务间通信和同步的机制。事件组允许多个任务等待一个或多个事件的组合&#xff0c;当这些事件的组合满足特定条件时&#xff0c;任务可以被唤醒。…

vb.netcad二开自学笔记2:认识vs编辑器

认识一下宇宙第一编辑器的界面图标含义还是很重要的&#xff0c;否则都不知道面对的是什么还怎么继续&#xff1f; 一、VS编辑器中常见的图标的含义 变量 长方体&#xff1a;变量 局部变量 两个矩形块&#xff1a;枚举 预定义的枚举 紫色立方体&#xff1a;方法 橙色树状结构…

UE4_材质_材质节点_Fresnel

学习笔记&#xff0c;不喜勿喷&#xff0c;侵权立删&#xff0c;祝愿生活越来越好&#xff01; 一、问题导入 在创建电影或过场动画时&#xff0c;你常常需要想办法更好地突显角色或场景的轮廓。这时你需要用到一种光照技术&#xff0c;称为边沿光照或边缘光照&#xff0c;它的…

Threejs环境、透视相机、坐标系、光源

文章目录 如何引入threejsnpm方式script方式script module方式 基本流程与坐标摄像机Geometry(几何体)和Material(材质)光源 如何引入threejs 对于很多刚刚上手threejs的朋友&#xff0c;可能第一步引入threejs就出问题了&#xff0c; 明明已经导入了&#xff0c;就是这样问题…

【搭建Nacos服务】centos7 docker从0搭建Nacos服务

前言 本次搭建基于阿里云服务器系统为&#xff08;CentOS7 Linux&#xff09;、Nacos&#xff08;2.0.3&#xff09;、Docker version 26.1.4 本次搭建基于一个新的云服务器 安装java yum install -y java-1.8.0-openjdk.x86_64安装驱动以及gcc等前置需要的命令 yum install …

【nvm管理nodejs版本,切换node指定版本】

nvm管理nodejs版本 nvm管理nodejs版本主要功能使用 nvm nvm管理nodejs版本 nvm&#xff08;Node Version Manager&#xff09;顾名思义node版本管理器&#xff0c;无须去node管网下载很多node安装程序;用于管理多个 Node.js 版本的工具。它允许你在同一台机器上同时安装和管理…

Appium启动APP时报错Security exception: Permission Denial

报错内容Security exception: Permission Denial: starting Intent 直接通过am命令尝试也是同样的报错 查阅资料了解到&#xff1a;android:exported | App quality | Android Developers exported属性默认false&#xff0c;所以android:exported"false"修改为t…

基于java+springboot+vue实现的图书商城管理系统(文末源码+Lw)283

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本图书商城管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信…

rpm包下载

内网无法下载、选择外网的一台机器下载rpm包 下载后上传rpm包 1、创建下载目录 mkdir /data/asap/test 2、下载能留存包的工具 sudo yum install yum-utils -y 报错就是环境问题没下载成功&#xff0c;我换了个环境正常的机器就可以了 3、下载rpm包到指定目录/data/asa…

测试人员如何管理项目与风险预警

在平时工作过程中&#xff0c;你有没有因项目延时&#xff0c;需求频繁变更&#xff0c;开发提测质量不高&#xff0c;以及漏测的情况下&#xff0c;背了不少锅的情况呢&#xff1f; 作为测试人员&#xff0c;我们应该如何发挥积极主动性&#xff0c;进行项目管理&#xff0c;有…

【学术会议征稿】2024年第十届机械制造技术与工程材料国际学术会议(ICMTEM 2024)

2024年第十届机械制造技术与工程材料国际学术会议&#xff08;ICMTEM 2024&#xff09; 2024 10th International Forum on Manufacturing Technology and Engineering Materials 第十届机械制造技术与工程材料国际学术会议&#xff08;ICMTEM 2024&#xff09;将于2024年10月…

(七)[重制]C++命名空间与标准模板库(STL)

​ 引言 在专栏C教程的第六篇C中的结构体与联合体中&#xff0c;介绍了C中的结构体和联合体&#xff0c;包括它们的定义、初始化、内存布局和对齐&#xff0c;以及作为函数参数和返回值的应用。在专栏C教程的第七篇中&#xff0c;我们将深入了解C中的命名空间&#xff08;nam…

linux 基础命令、gcc的基础用法

1、ls——>列出目录下的内容 语法&#xff1a;ls [-a -l -h] [Linux路径] &#xff08;1&#xff09;-a -l -h 是可选的选项 &#xff08;2&#xff09;Linux路径是此命令的可选参数 ①当不使用选项和参数&#xff0c;直接使用 ls 命令本体&#xff0c;表示&#xff1a;…

kubernetes集群部署:环境准备及master节点部署(二)

主机名IPv4地址IPv6地址角色安装组件操作系统k8s130-node190192.168.XX.190240a:XX::190masterkubeadm、kubelet、containerdAnolis OS 8.94.19.91-28.1.an8.x86_64k8s130-node191192.168.XX.191240a:XX::191nodekubeadm、kubelet、cri-oAnolis OS 8.94.19.91-28.1.an8.x86_64k…

Java基础-接口与实现

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 Java 接口 什么是接口&#xff1f; 声明接口 实现接口 继承接口 接口的多继承 标记接口 Java 接口 …

华为仓颉可以取代 Java 吗?

大家好&#xff0c;我是君哥。 在最近的华为开发者大会上&#xff0c;华为亮相了仓颉编程语言&#xff0c;这是华为历经 5 年&#xff0c;投入大量研发成本沉淀的一门编程语言。 1 仓颉简介 按照官方报告&#xff0c;仓颉编程语言是一款面向全场景智能的新一代编程语言&#…

好消息!Stable Diffusion 3 允许商业化,很快开源更大版本模型

7月6日凌晨&#xff0c;著名开源大模型平台Stability AI修改了社区许可协议&#xff0c;最新发布的文生图模型Stable Diffusion 3 Medium允许商业化&#xff08;以下简称“SD3-M”&#xff09;。 如果企业、个人开发者每年收入低于100万美元&#xff08;大约726万元人民币&…

《安全行业大模型技术应用态势发展报告(2024)》

人工智能技术快速迭代发展&#xff0c;大模型应用场景不断拓展&#xff0c;随着安全行业对人工智能技术的应用程度日益加深&#xff0c;大模型在网络安全领域的应用潜力和挑战逐渐显现。安全行业大模型技术的应用实践不断涌现&#xff0c;其在威胁检测、风险评估和安全运营等方…

腐蚀服务器如何设置管理员

可以设置服主与管理员 控制台中设置&#xff08;需游戏账号在线&#xff09; 服主 添加&#xff1a;在控制台中输入ownerid空格SteamID 删除&#xff1a;在控制台中输入removeowner空格SteamID 管理员 添加&#xff1a;在控制台中输入moderatorid空格SteamID 删除&#…