碰到一个树形数据需要存储再数据控制,碰到以下两个问题:

  • 在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 优雅存储树形数据的更多相关文章

  1. Atitit 研发体系建立 数据存储与数据知识点体系知识图谱attilax 总结

    Atitit 研发体系建立 数据存储与数据知识点体系知识图谱attilax 总结 分类具体知识点原理规范具体实现(oracle,mysql,mssql是否可以自己实现说明 数据库理论数据库的类型 数据 ...

  2. Web中树形数据(层级关系数据)的实现—以行政区树为例

    在Web开发中常常遇到树形数据的操作,如菜单.组织机构.行政区(省.市.县)等具有层级关系的数据. 以下以行政区为例说明树形数据(层级关系数据)的存储以及实现,效果如图所看到的. 1 数据库表结构设计 ...

  3. Mysql通过Adjacency List(邻接表)存储树形结构

    转载自:https://www.jb51.net/article/130222.htm 以下内容给大家介绍了MYSQL通过Adjacency List (邻接表)来存储树形结构的过程介绍和解决办法,并 ...

  4. SqlServer 递归查询树形数据

    一直没有在意过数据库处理树形数据的重要性,直到有一天朋友问起我关于树形数据查询的问题时才发现根本不会,正好这个时候也要用到递归进行树形数据的查询于是在网上查了一圈,语法总结如下 参考文献:https: ...

  5. treeGrid树形数据表格的json数据格式说明

    在使用easyUI 的treeGrid的时候,很多时候我们从数据库取出来的数据treeGrid却不能读取显示成一个树:如下 { menuCode: "a00", menuName: ...

  6. 数据库中用varbinary存储二进制数据

    问题描述:将图片.二进制文件内容等数据存储在数据库中,并能从数据库中取出还原为图片或文件,数据库存储二进制数据用varbinary字段. 分析:由于之前数据库中没有用过varbinary存储数据,首先 ...

  7. Atitit 数据存储的数据表连接attilax总结

    Atitit 数据存储的数据表连接attilax总结 1.1. 三种物理连接运算符:嵌套循环连接.合并连接以及哈希连接1 1.2. a.嵌套循环连接(nested loops join)1 1.3. ...

  8. cocos2d-x中使用可加密Sqlite存储玩家数据

    手机游戏当中的数据存储是一个重要的课题.cocos2d-x发展到现在的版本2.1.4,已经直接实现了对sqlite的支持(extensions/LocalStorage),这对我们一般的数据存储已经够 ...

  9. NoSql存储日志数据之Spring+Logback+Hbase深度集成

    NoSql存储日志数据之Spring+Logback+Hbase深度集成 关键词:nosql, spring logback, logback hbase appender 技术框架:spring-d ...

随机推荐

  1. 关于ios的光标和键盘回弹问题

    最近再做项目的时候(移动端),遇到了两个小问题,一个是ios端键盘不回弹的问题(微信浏览器),另一个是ios输入光标位置问题.首先看第二个问题: 就是点击一个按钮,要把输入框里面原来的内容加上一些固定 ...

  2. 高通QCC3026蓝牙音频芯片处理器介绍

    QCC3026是一款基于超低功耗架构的入门级闪存可编程蓝牙音频SoC,专为紧凑型功能优化的Qualcomm TrueWireless耳塞而设计.QCC3026旨在为我们的客户提供有助于缩短开发时间并具 ...

  3. devm_xxx机制【转】

    前言 devm是内核提供的基础机制,用于方便驱动开发者所分配资源的自动回收.参考内核文档devres.txt.总的来说,就是驱动开发者只需要调用这类接口分配期望的资源,不用关心释放问题.这些资源的释放 ...

  4. cmd命令重定向到剪切板

    Windows下 使用系统自带的 clip 命令. # 位于 C:\Windows\system32\clip.exe. 示例: # 将字符串 Hello 放入 Windows 剪贴板 echo He ...

  5. python3 集合 操作方法

    今天都是课, 就学这么点  不多说 睡觉睡觉!!! #!usr/bin/env/ python # -*- coding:utf-8 -*- # Author:XiaoFeng list = [1,2 ...

  6. jdbc连接数据库,中文出现乱码的问题

    一.使用jdbc连接数据库,插入数据库时,数据里的数据显示乱码,为 " ??? " 两种解决方案: 1.修改服务端的mysql配置文件,编辑my.cnf文件,在[mysqld]下添 ...

  7. gin+gorm

    在[环境变量]中添加如下[用户变量]/[系统变量]:GO111MODULE,值为on go mod init目录 在项目中新建文件main.go,并添加测试代码 package main import ...

  8. Luogu P5290 [十二省联考2019]春节十二响

    这题是最近看到的今年省选题中最良心的一道了吧 看题+想题+写题都可以在0.5h内解决,送分含义明显啊 首先理解了题意后我们很快就能发现两个点如果要被分在一段那么必须在它们的祖先处合并 首先我们考虑下二 ...

  9. Koa 中的错误处理

    不像 express 中在末尾处注册一个声明为 (err, req, res, next) 中间件的方式,koa 刚好相反,在开头进行注册. app.use(async (ctx, next) =&g ...

  10. 知识小罐头07(tomcat8请求源码分析 下)

    感觉最近想偷懒了,哎,强迫自己也要写点东西,偷懒可是会上瘾的,嘿嘿!一有写博客的想法要赶紧行动起来,养成良好的习惯. ok,继续上一篇所说的一些东西,上一篇说到Connector包装了那两个对象,最后 ...