Postgres 优雅存储树形数据
碰到一个树形数据需要存储再数据控制,碰到以下两个问题:
- 在PG数据库中如何表达树形数据
- 如何有效率的查询以任意节点为Root的子树
测试数据
为了更加简单一些,我们将使用一下数据
Section A
|--- Section A.1
Section B
|--- Section B.1
|--- Section B.1
|--- Section B.1.1
简单的自引用
当设计自引用表(有时候自己join自己)。最简单明了的就是有一个parent_id字段。
CREATE TABLE section (
id INTEGER PRIMARY KEY,
name TEXT,
parent_id INTEGER REFERENCES section,
);
ALTER TABLE page ADD COLUMN parent_id INTEGER REFERENCES page;
CREATE INDEX section_parent_id_idx ON section (parent_id);
然后插入一些样例数据,用parent_id来关联其他节点
INSERT INTO section (id, name, parent_id) VALUES (1, 'Section A', NULL);
INSERT INTO section (id, name, parent_id) VALUES (2, 'Section A.1', 1);
INSERT INTO section (id, name, parent_id) VALUES (3, 'Section B', NULL);
INSERT INTO section (id, name, parent_id) VALUES (4, 'Section B.1', 3);
INSERT INTO section (id, name, parent_id) VALUES (5, 'Section B.2', 3);
INSERT INTO section (id, name, parent_id) VALUES (6, 'Section B.2.1', 5);
再进行一些简单的查询时,这个方法非常好使。比如我们要查询Section B的所有一级子节点
SELECT * FROM section WHERE parent = 3
但是如果要做复杂一些的查询时,就很蛋疼了,查询中会有许多复杂和递归的问题。比如我们要查询Section B的所有子节点
WITH RECURSIVE nodes(id,name,parent_id) AS (
SELECT s1.id, s1.name, s1.parent_id
FROM section s1 WHERE parent_id = 3
UNION
SELECT s2.id, s2.name, s2.parent_id
FROM section s2, nodes s1 WHERE s2.parent_id = s1.id
)
SELECT * FROM nodes;
这种方案解决了第一个问题,但是没有解决第二个问题(高效的找到子树)
Ltree extension
ltree extension来查询树形数据是个不错的选择,在自引用的关系表中表现的更加优秀。用ltree重新建一个表。我将用每一个section的主键作为ltree路径中的标识。用root标识顶节点。
CREATE EXTENSION ltree;
CREATE TABLE section (
id INTEGER PRIMARY KEY,
name TEXT,
parent_path LTREE
);
CREATE INDEX section_parent_path_idx ON section USING GIST (parent_path);
INSERT INTO section (id, name, parent_path) VALUES (1, 'Section 1', 'root');
INSERT INTO section (id, name, parent_path) VALUES (2, 'Section 1.1', 'root.1');
INSERT INTO section (id, name, parent_path) VALUES (3, 'Section 2', 'root');
INSERT INTO section (id, name, parent_path) VALUES (4, 'Section 2.1', 'root.3');
INSERT INTO section (id, name, parent_path) VALUES (4, 'Section 2.2', 'root.3');
INSERT INTO section (id, name, parent_path) VALUES (5, 'Section 2.2.1', 'root.3.4');
OK,一切搞定,我们可以用ltree操作符@>和<@来查询Section B的所有子节点
SELECT * FROM section WHERE parent_path <@ 'root.3';
但是还是有一些小问题:
- 在
parent_id这种方案中,我们有外键约束来维系节点之间的关系,但是在Ltree版本中我们是没有这种约束的 - 维户这个树每一个路径都是有效,这其实是非常痛苦的。比如你的树变大了,比如你操作的是很久之前的树。总之搞不好有时候你查出来的是孤儿节点
最终解决方案
为了解决上章的两个小问题,我们需要一种混搭(有parent_id还要高效易于维护)。为了达到这个目标,我们设计一个trigger来封装树操作的过程,更新树仅仅靠更新parent_id。
CREATE EXTENSION ltree;
CREATE TABLE section (
id INTEGER PRIMARY KEY,
name TEXT,
parent_id INTEGER REFERENCES section,
parent_path LTREE
);
CREATE INDEX section_parent_path_idx ON section USING GIST (parent_path);
CREATE INDEX section_parent_id_idx ON section (parent_id);
CREATE OR REPLACE FUNCTION update_section_parent_path() RETURNS TRIGGER AS $$
DECLARE
path ltree;
BEGIN
IF NEW.parent_id IS NULL THEN
NEW.parent_path = 'root'::ltree;
ELSEIF TG_OP = 'INSERT' OR OLD.parent_id IS NULL OR OLD.parent_id != NEW.parent_id THEN
SELECT parent_path || id::text FROM section WHERE id = NEW.parent_id INTO path;
IF path IS NULL THEN
RAISE EXCEPTION 'Invalid parent_id %', NEW.parent_id;
END IF;
NEW.parent_path = path;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER parent_path_tgr
BEFORE INSERT OR UPDATE ON section
FOR EACH ROW EXECUTE PROCEDURE update_section_parent_path();
这样就爽多了.^_^.
Postgres 优雅存储树形数据的更多相关文章
- Atitit 研发体系建立 数据存储与数据知识点体系知识图谱attilax 总结
Atitit 研发体系建立 数据存储与数据知识点体系知识图谱attilax 总结 分类具体知识点原理规范具体实现(oracle,mysql,mssql是否可以自己实现说明 数据库理论数据库的类型 数据 ...
- Web中树形数据(层级关系数据)的实现—以行政区树为例
在Web开发中常常遇到树形数据的操作,如菜单.组织机构.行政区(省.市.县)等具有层级关系的数据. 以下以行政区为例说明树形数据(层级关系数据)的存储以及实现,效果如图所看到的. 1 数据库表结构设计 ...
- Mysql通过Adjacency List(邻接表)存储树形结构
转载自:https://www.jb51.net/article/130222.htm 以下内容给大家介绍了MYSQL通过Adjacency List (邻接表)来存储树形结构的过程介绍和解决办法,并 ...
- SqlServer 递归查询树形数据
一直没有在意过数据库处理树形数据的重要性,直到有一天朋友问起我关于树形数据查询的问题时才发现根本不会,正好这个时候也要用到递归进行树形数据的查询于是在网上查了一圈,语法总结如下 参考文献:https: ...
- treeGrid树形数据表格的json数据格式说明
在使用easyUI 的treeGrid的时候,很多时候我们从数据库取出来的数据treeGrid却不能读取显示成一个树:如下 { menuCode: "a00", menuName: ...
- 数据库中用varbinary存储二进制数据
问题描述:将图片.二进制文件内容等数据存储在数据库中,并能从数据库中取出还原为图片或文件,数据库存储二进制数据用varbinary字段. 分析:由于之前数据库中没有用过varbinary存储数据,首先 ...
- Atitit 数据存储的数据表连接attilax总结
Atitit 数据存储的数据表连接attilax总结 1.1. 三种物理连接运算符:嵌套循环连接.合并连接以及哈希连接1 1.2. a.嵌套循环连接(nested loops join)1 1.3. ...
- cocos2d-x中使用可加密Sqlite存储玩家数据
手机游戏当中的数据存储是一个重要的课题.cocos2d-x发展到现在的版本2.1.4,已经直接实现了对sqlite的支持(extensions/LocalStorage),这对我们一般的数据存储已经够 ...
- NoSql存储日志数据之Spring+Logback+Hbase深度集成
NoSql存储日志数据之Spring+Logback+Hbase深度集成 关键词:nosql, spring logback, logback hbase appender 技术框架:spring-d ...
随机推荐
- 用CSS画一个带阴影的三角形的示例代码
1. 思路 怎么用CSS3画一个带阴影的三角形呢 ? 有童鞋说, 这还不简单吗 网上有很多解决方案, 但其实大多都是实现不太完美的, 存在一些问题 假设我们做一个向下的三角形箭头 常见的方法大致有两种 ...
- Android路由框架-ARouter详解
文章大纲 一.页面路由基本介绍1.什么是页面路由2.为什么要使用页面路由二.页面路由框架ARouter介绍1.常用功能介绍2.常见应用场景三.源码下载四.参考文章 一.页面路由基本介绍 1.什么是 ...
- [ gczdac ] 20190306 访者必阅
1.我的私人博客!!! 2.博主还处于入门学习阶段,并且非常玻璃心 3. 激烈讨论√ 人身攻击× 4. 欢迎指正错误,相互学习 5. 很少在线,无法及时回复请谅解 https://www.cnblo ...
- nginx 安装、启动、重启、关闭 (linux系统命令行)
前言: 最近在部署我的hexo静态博客到腾讯云服务器上,用到了很多nginx的知识,在此做下总结: 刚接触的linux服务器上,nginx配置乱的有点令人发指,就把老的卸载了重新装一下. 1.卸载 y ...
- SpringBoot2.0之八 多数据源配置
在开发的过程中我们可能都会遇到对接公司其他系统等需求,对于外部的系统可以采用接口对接的方式,对于一个公司开发的两个系统,并且知道相关数据库结构的情况下,就可以考虑使用多数据源来解决这个问题.Spri ...
- WinForm 工作流设计 1
从事软件行业那么多年,一直很少写博.很多技术,长时间不用都慢慢淡忘. 把自己学到的用笔记下来,可以巩固和发现不足,也可以把自己对技术的一些 理解,分享出来供大家批评指正. 废话不多说,进入正题.工作流 ...
- windows下 jdk1.7安装教程图解
java编程的初学者在开始编码前都会遇到一个难题,那就是jdk1.7环境变量配置怎么操作,怎么安装,针对这个难题,小编特地为大家整理相关教程,不了解的朋友可以前往查看使用. 软件说明 虽然现在jdk出 ...
- JAVA之enum类详解
目录 一.简介 二.默认枚举类 三.多值枚举类 四.属性和方法 五.构造函数 六.重要方法 七.引用参考 一.简介 1.枚举类代表一组常量: 2. ...
- 【TensorFlow篇】--Tensorflow框架实现SoftMax模型识别手写数字集
一.前述 本文讲述用Tensorflow框架实现SoftMax模型识别手写数字集,来实现多分类. 同时对模型的保存和恢复做下示例. 二.具体原理 代码一:实现代码 #!/usr/bin/python ...
- 中文分词实战——基于jieba动态加载字典和调整词频的电子病历分词
分词是自然语言处理中最基本的一个任务,这篇小文章不介绍相关的理论,而是介绍一个电子病历分词的小实践. 开源的分词工具中,我用过的有jieba.hnlp和stanfordnlp,感觉jieba无论安装和 ...