Closure Table-树形多级关系数据库设计(MySql)

一般树形多级关系数据库设计,比较普遍的就是四种方法:(具体见 SQL Anti-patterns这本书)

Adjacency List:每一条记录存parent_id

Path Enumerations:每一条记录存整个tree path经过的node枚举(适合深度较浅且固定的业务,字段超长互后将无法命中索引)

Nested Sets:每一条记录存 nleft 和 nright

Closure Table:维护一个表,所有的tree path作为记录进行保存。

本文将以Adjacency List作为原始记录,Closure Table存储上下级关系的方式来解决查询困难问题。

本文限定:删除节点时,仅可删除最末节点,不可从中间删除,否则树不能称之为树。

一:通过parent_id方式保存原始记录

1:如图,现有以下关系

以数据库保存关系如下:

仅以当前表结构,如果仅仅是查询上下级关系,将非常简单,如:

sql 复制代码
-- 查询西湖区的下级

select * from t_area where parent_id = 330106;

-- 查询西湖区的上级

select * from t_area where id = ( select parent_id from t_arem where id = 330106 )

但是,如果需要查询杭州市的所有下级单位,必须使用递归查询:

如果此时需求是查询"西溪街道"与"杭州市"是否是上下级关系? 那么每次查询都需要递归关系吗?

此时,需要引入Closure Table模式创建上下级关系。

二:创建ClosureTable关系表

结构如下:

数据如下:

此时,如果查询杭州市所有下级单位,SQL如下:

"西溪街道"与"杭州市"是否是上下级关系?

三 :ClosureTable详细使用方法

1:新增数据

如上图,在下城区新增望江街道。

代码如下:

sql 复制代码
-- 1,新增父子关系
INSERT INTO t_area(id,name,parent_id) VALUES(330102010,'望江街道',330102);

-- 2,新增上下级关系
-- 2.1 插入自身
INSERT INTO t_area_closure(ancestor,descendant,level) VALUES(330102010,330102010,0);

-- 2.2 为 330102010 的父级 330102 所有上级(包含330102自身)增加下级330102010血缘关系。
INSERT INTO t_area_closure(ancestor,descendant,level)
select ancestor,330102010 as descendant,(level+1) as level from t_area_closure where descendant =330102;

此时,查询330102010的上级关系:(如果不包含自己,则筛选level>0即可)

2:删除数据

如上图,删除杭州市与江干区的关系。

代码如下:

sql 复制代码
-- 1,删除父子关系(也可以增加逻辑字段进行软删除)
DELETE FROM t_area where id =330104;

-- 2,删除关系表。因为是末端删除,只要删除指定数据的父级关系即可

DELETE FROM t_area_closure where descendant = 330104;

此时,关系表中已没有330104数据的关系数据

3:变更父子关系

如上图,将西湖区转移到望江街道下面。

简单做法:

删除老的关系。将西湖区、北山街道、西溪街道,按删除数据处理,删除所有关系。

新增关系。在望江街道下面新增西湖区、北山街道、西溪街道关系。

复杂做法:

1,删除老关系。

西湖区、北山街道、西溪街道与杭州市、浙江省的关系将会变更,所以先删除西湖区、北山街道、西溪街道与杭州市、浙江省的上级关系。即,西湖区及下属区域删除西湖区父级以上区域的关系。

sql 复制代码
-- 1.1 查找指定区域的父级及以上关系
SELECT ancestor from t_area_closure where descendant = 330106 and level >0
-- 1.2 查找区域自己和自己的下级
SELECT descendant from t_area_closure where ancestor = 330106
-- 1.3删除自己和自己的下级 与 自己父级以上区域的关系
delete from t_area_closure where ancestor in ( select ancestor from (SELECT ancestor from t_area_closure where descendant = 330106 and level >0) t1 )
and descendant in ( select descendant from (SELECT descendant from t_area_closure where ancestor = 330106) t2 )

此时删除完成数据后,西湖区自己与下属北山街道、西溪街道的关系依然存在。

2,添加新关系。

2.1 查询新父级的上级关系(包含新父级)

2.2 查找区域自己和自己的下级

2.3 为指定区域自己及下级增加新父级的上级关系。

sql 复制代码
INSERT INTO t_area_closure(ancestor,descendant,level)
SELECT t1.ancestor,t2.descendant,(t1.level+t2.`level` +1) as level from t_area_closure t1,t_area_closure t2 
where t1.descendant =330102010 and t2.ancestor = 330106;

此时,查询北山街道所有上级:

数据正确。

相关推荐
夜泉_ly2 小时前
MySQL -安装与初识
数据库·mysql
qq_529835353 小时前
对计算机中缓存的理解和使用Redis作为缓存
数据库·redis·缓存
月光水岸New6 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6756 小时前
数据库基础1
数据库
我爱松子鱼6 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo6 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser7 小时前
【SQL】多表查询案例
数据库·sql
Galeoto7 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
人间打气筒(Ada)8 小时前
MySQL主从架构
服务器·数据库·mysql
leegong231118 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql