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 ...
随机推荐
- 3.JAVA-方法重载,类的封装,构造/析构方法
1.方法重载 和C++的函数重载一样,主要是实现多个相同的函数名,但是参数表不同. 参数表不同主要有以下几种 1) 参数个数不同 2) 参数类型不同 3) 参数顺序不同 2.类和对象 类class 用 ...
- 浅谈java线程池实现
再进入主题之前,我们先了解几个概念,对读源码有所帮助,对于线程池的运行状态,有4个级别,分别是RUNNING,SHUTING,STOP,TIDING,TERMINATED 解释如下: The runS ...
- 微信小程序echarts层级太高
项目中因为需求,底部的tab导航栏是自己写的,在开发者工具中一切正常:但是在真机上页面滑动时,echarts的层级比tab高,调过两者的z-index后仍然如此. 经过查找后发现cover-view和 ...
- android 请求接口报错 org.apache.http.conn.HttpHostConnectException: Connection to http://192.168.1.90:9090 refused
No Network Security Config specified, using platform defaultI/System.out: org.apache.http.conn.HttpH ...
- 工程实践:给函数取一个"好"的名字
工程实践:给函数取一个"好"的名字 早在2013年,国外有个程序员做了一个有意思的投票统计(原始链接请见:<程序员:你认为最难做的事情是什么?>),该投票是让程序员从以 ...
- Android 平台 Native 代码的崩溃捕获机制及实现
本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/g-WzYF3wWAljok1XjPoo7w 一.背景 ...
- 关于微信企业付款到零钱X509Certificate2读取证书信息,发布到服务器访问不到的解决方案
前言: 最近做了一个通过调用微信企业付款到用户零钱的功能,真的挺奇怪的,在我本地调试的时候都没有问题,但是当我发布到服务上的时候却一直无法读取到我的证书信息.读取的代码如下,使用的是微信官方文档提供 ...
- Servlet+JSP及Tomcat常见面试题(面试必备)
1. 什么是servlet? servlet是用来处理客户端请求并产生动态网页内容的java类 2. Tomcat的缺省端口是多少,怎么修改? a) 默认端口号是8080 b) ...
- GetPathFromUri4kitkat【Android 4.4 kitkat以上及以下根据uri获取路径的方法】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 在Android4.4之前和之后,通过Intent调用文件管理器选择文件,获取的文件uri地址形式是不同的. Android6.0 ...
- MyWebViewDemo【封装Webview常用配置和选择文件、打开相机、录音、打开本地相册的用法】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 封装webview的常用配置和选择文件.打开相机.录音.打开本地相册的用法.[如果想要使用简单的预览功能,可以参考<MyBri ...