浅谈MYSQL中树形结构表3种设计优劣分析与分享

目录
  • 简介
  • 问题
  • 设计1:邻接表
    • 表设计
    • SQL示例
  • 设计2:路径枚举
    • 表设计
    • SQL示例
  • 设计3:闭包表
    • 表设计
    • SQL示例
  • 结合使用
    • 表设计
  • 总结

简介

在开发中经常遇到树形结构的场景,本文将以部门表为例对比几种设计的优缺点;

问题

需求背景:根据部门检索人员,
问题:选择一个顶级部门情况下,跨级展示当前部门以及子部门下的所有人员,表怎么设计更合理 ?

递归吗 ?递归可以解决,但是势必消耗性能

设计1:邻接表

注:(常见父Id设计)

表设计

CREATE TABLE `dept_info01` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `dept_id` int(10) NOT NULL COMMENT '部门id',
  `dept_name` varchar(100) NOT NULL COMMENT '部门名称',
  `dept_parent_id` int(11) NOT NULL COMMENT '父部门id',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

这样是最常见的设计,能正确的表达菜单的树状结构且没有冗余数据,但在跨层级查询需要递归处理。

SQL示例

1.查询某一个节点的直接子集

SELECT * FROM dept_info01  WHERE dept_parent_id =1001

优点

结构简单 ;

缺点

1.不使用递归情况下无法查询某节点所有父级,所有子集

设计2:路径枚举

在设计1基础上新增一个父部门id集字段,用来存储所有父集,多个以固定分隔符分隔,比如逗号。

表设计

CREATE TABLE `dept_info02` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `dept_id` int(10) NOT NULL COMMENT '部门id',
  `dept_name` varchar(100) NOT NULL COMMENT '部门名称',
  `dept_parent_id` int(11) NOT NULL COMMENT '父部门id',
  `dept_parent_ids` varchar(255) NOT NULL DEFAULT '' COMMENT '父部门id集',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

SQL示例

1.查询所有子集
1).通过模糊查询

SELECT
 *
FROM
	dept_info02
WHERE
	dept_parent_ids like '%1001%'

2).推荐使用 FIND_IN_SET 函数

SELECT
	*
FROM
	dept_info02
WHERE
	FIND_IN_SET( '1001', dept_parent_ids )

优点

  • 方便查询所有的子集 ;
  • 可以因此通过比较字符串dept_parent_ids长度获取当前节点层级 ;

缺点

  • 新增节点时需要将dept_parent_ids字段值处理好 ;
  • dept_parent_ids字段的长度很难确定,无论长度设为多大,都存在不能够无限扩展的情况 ;节
  • 点移动复杂,需要同时变更所有子集中的dept_parent_ids字段值 ;

设计3:闭包表

  • 闭包表是解决分级存储的一个简单而优雅的解决方案,这是一种通过空间换取时间的方式 ;
  • 需要额外创建了一张TreePaths表它记录了树中所有节点间的关系 ;
  • 包含两列,祖先列与后代列,即使这两个节点之间不是直接的父子关系;同时增加一行指向节点自己 ;

表设计

主表

CREATE TABLE `dept_info03` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `dept_id` int(10) NOT NULL COMMENT '部门id',
  `dept_name` varchar(100) NOT NULL COMMENT '部门名称',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

祖先后代关系表

CREATE TABLE `dept_tree_path_info` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `ancestor` int(10) NOT NULL COMMENT '祖先id',
  `descendant` int(10) NOT NULL COMMENT '后代id',
  `depth` tinyint(4) NOT NULL DEFAULT '0' COMMENT '层级深度',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

注:depth 层级深度字段 ,自我引用为 1,直接子节点为 2,再一下层为 3,一次类推,第几层就是几 。

SQL示例

插入新节点

INSERT INTO dept_tree_path_info (ancestor, descendant,depth)
SELECT t.ancestor, 3001,t.depth+1 FROM dept_tree_path_info AS t
WHERE t.descendant = 2001
UNION ALL
SELECT 3001,3001,1

查询所有祖先

SELECT
	c.*
FROM
	dept_info03 AS c
INNER JOIN dept_tree_path_info t ON c.dept_id = t.ancestor
WHERE
	t.descendant = 3001

查询所有后代

SELECT
	c.*
FROM
	dept_info03 AS c
INNER JOIN dept_tree_path_info t ON c.dept_id = t.descendant
WHERE
t.ancestor = 1001

删除所有子树

DELETE
FROM
	dept_tree_path_info
WHERE
	descendant IN
	(
		SELECT
			a.dept_id
		FROM
		( SELECT descendant dept_id FROM dept_tree_path_info WHERE  ancestor = 1001 ) a
	)

删除叶子节点

DELETE
FROM
	dept_tree_path_info
WHERE
	descendant = 2001

移动节点

  • 删除所有子树(先断开与原祖先的关系)
  • 建立新的关系

优点

  • 非递归查询减少冗余的计算时间 ;
  • 方便非递归查询任意节点所有的父集 ;
  • 方便查询任意节点所有的子集 ;
  • 可以实现无限层级 ;
  • 支持移动节点 ;

缺点

  • 层级太多情况下移动树节点会带来关系表多条操作 ;
  • 需要单独一张表存储对应关系,在新增与编辑节点时操作相对复杂 ;

结合使用

可以将邻接表方式与闭包表方式相结合使用。实际上就是将父id冗余到主表中,在一些只需要查询直接关系的业务中就可以直接查询主表,而不需要关联2张表了。在需要跨级查询时祖先后代关系表就显得尤为重要。

表设计

主表

CREATE TABLE `dept_info04` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `dept_id` int(10) NOT NULL COMMENT '部门id',
  `dept_name` varchar(100) NOT NULL COMMENT '部门名称',
  `dept_parent_id` int(11) NOT NULL COMMENT '父部门id',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

祖先后代关系表

CREATE TABLE `dept_tree_path_info` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `ancestor` int(10) NOT NULL COMMENT '祖先id',
  `descendant` int(10) NOT NULL COMMENT '后代id',
  `depth` tinyint(4) NOT NULL DEFAULT '0' COMMENT '层级深度',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

总结

其实,在以往的工作中,曾见过不同类型的设计,邻接表,路径枚举,邻接表路径枚举一起来的都见过。每种设计都各有优劣,如果选择设计依赖于应用程序中哪种操作最需要性能上的优化。

设计 表数量 查询直接子 查询子树 同时查询多个节点子树 插入 删除 移动
邻接表 1 简单 需要递归 需要递归 简单 简单 简单
枚举路径 1 简单 简单 查多次 相对复杂 简单 复杂
闭包表 2 简单 简单 简单 相对复杂 简单 复杂

综上所述

  • 只需要建立子父集关系中可以使用邻接表方式 ;
  • 涉及向上查找,向下查找的需要建议使用闭包表方式 ;

到此这篇关于浅谈MYSQL中树形结构表3种设计优劣分析与分享的文章就介绍到这了,更多相关MYSQL 树形结构表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈mysql 树形结构表设计与优化

    前言 在诸多的管理类,办公类等系统中,树形结构展示随处可见,以"部门"或"机构"来说,接触过的同学应该都知道,最终展示到页面的效果就是层级结构的那种,下图随机列举了一个部门的树型结构展示图 设计考虑因素 1.表结构设计 稍稍有点开发和表结构设计经验的同学,设计出这样一张表,应该很容易,只需要在depart表中,添加一个pid/字段即可满足要求,参考下表: CREATE TABLE `depart` ( `depart_id` varchar(32) NOT NULL

  • 浅谈MYSQL中树形结构表3种设计优劣分析与分享

    目录 简介 问题 设计1:邻接表 表设计 SQL示例 设计2:路径枚举 表设计 SQL示例 设计3:闭包表 表设计 SQL示例 结合使用 表设计 总结 简介 在开发中经常遇到树形结构的场景,本文将以部门表为例对比几种设计的优缺点: 问题 需求背景:根据部门检索人员, 问题:选择一个顶级部门情况下,跨级展示当前部门以及子部门下的所有人员,表怎么设计更合理 ? 递归吗 ?递归可以解决,但是势必消耗性能 设计1:邻接表 注:(常见父Id设计) 表设计 CREATE TABLE `dept_info01

  • 浅谈JavaScript构造树形结构的一种高效算法

    引言 我们经常会碰到树形数据结构,比如组织层级.省市县或者动植物分类等等数据.下面是一个树形结构的例子: 在实际应用中,比较常见的做法是将这些信息存储为下面的结构,特别是当存在1对多的父/子节点关系时: const data = [ { id: 56, parentId: 62 }, { id: 81, parentId: 80 }, { id: 74, parentId: null }, { id: 76, parentId: 80 }, { id: 63, parentId: 62 }, {

  • 浅谈Mysql中类似于nvl()函数的ifnull()函数

    IFNULL(expr1,expr2) 如果expr1不是NULL,IFNULL()返回expr1,否则它返回expr2.IFNULL()返回一个数字或字符串值,取决于它被使用的上下文环境. mysql> select IFNULL(1,0); -> 1 mysql> select IFNULL(0,10); -> 0 mysql> select IFNULL(1/0,10); -> 10 mysql> select IFNULL(1/0,'yes'); ->

  • 浅谈mysql中多表不关联查询的实现方法

    大家在使用MySQL查询时正常是直接一个表的查询,要不然也就是多表的关联查询,使用到了左联结(left join).右联结(right join).内联结(inner join).外联结(outer join).这种都是两个表之间有一定关联,也就是我们常常说的有一个外键对应关系,可以使用到 a.id = b.aId这种语句去写的关系了.这种是大家常常使用的,可是有时候我们会需要去同时查询两个或者是多个表的时候,这些表又是没有互相关联的,比如要查user表和user_history表中的某一些数据

  • 浅谈MySQL中授权(grant)和撤销授权(revoke)用法详解

    MySQL 赋予用户权限命令的简单格式可概括为: grant 权限 on 数据库对象 to 用户 一.grant 普通数据用户,查询.插入.更新.删除 数据库中所有表数据的权利 grant select on testdb.* to common_user@'%' grant insert on testdb.* to common_user@'%' grant update on testdb.* to common_user@'%' grant delete on testdb.* to c

  • 浅谈MySQL中的自增主键用完了怎么办

    在面试中,大家应该经历过如下场景 面试官:"用过mysql吧,你们是用自增主键还是UUID?" 你:"用的是自增主键" 面试官:"为什么是自增主键?" 你:"因为采用自增主键,数据在物理结构上是顺序存储,性能最好,blabla-" 面试官:"那自增主键达到最大值了,用完了怎么办?" 你:"what,没复习啊!!"    (然后,你就可以回去等通知了!) 这个问题是一个粉丝给我提的,我觉得

  • 浅谈MySQL中group_concat()函数的排序方法

    group_concat()函数的参数是可以直接使用order by排序的.666.. 下面通过例子来说明,首先看下面的t1表. 比如,我们要查看每个人的多个分数,将该人对应的多个分数显示在一起,分数要从高到底排序. 可以这样写: SELECT username,GROUP_CONCAT(score ORDER BY score DESC) AS myScore FROM t1 GROUP BY username; 效果如下: 以上这篇浅谈MySQL中group_concat()函数的排序方法就

  • 浅谈mysql中concat函数,mysql在字段前/后增加字符串

    MySQL中concat函数 使用方法: CONCAT(str1,str2,-) 返回结果为连接参数产生的字符串.如有任何一个参数为NULL ,则返回值为 NULL. 注意: 如果所有参数均为非二进制字符串,则结果为非二进制字符串. 如果自变量中含有任一二进制字符串,则结果为一个二进制字符串. 一个数字参数被转化为与之相等的二进制字符串格式:若要避免这种情况,可使用显式类型 cast, 例如: SELECT CONCAT(CAST(int_col AS CHAR), char_col) MySQ

  • 浅谈MySQL中float、double、decimal三个浮点类型的区别与总结

    下表中规划了每个浮点类型的存储大小和范围: 类型 大小 范围(有符号) 范围(无符号) 用途 ==float== 4 bytes (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) 0,(1.175 494 351 E-38,3.402 823 466 E+38) 单精度 浮点数值 ==double== 8 bytes (-1.797 693 134 862 315 7 E

  • 浅谈MySQL中的group by

    目录 1.前言 2.准备user表 2.1 group by规则 2.2 group by使用 2.3 having使用 2.4 order by与limit 2.5 with rollup 1.前言 MySQL的group by用于对查询的数据进行分组:此外MySQL提供having子句对分组内的数据进行过滤. MySQL提供了许多select子句关键字, 它们在语句中的顺序如下所示: 子句 作用 是否必须/何时使用 select 查询要返回的数据或者表达式 是 from 指定查询的表 否 w

随机推荐