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

  • 在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. 学习笔记—HTML基础标签

    HTML的概念 概念: HTML 是用来描述网页的一种语言. HTML 指的是超文本标记语言 (Hyper Text Markup Language) HTML 不是一种编程语言,而是一种标记语言 ( ...

  2. Java关于数字工具类~持续汇总~

    /** * 01 * 描述:求int数组中最大值 * [时间 2019年3月5日下午3:21:36 作者 陶攀峰] */ public static int test01(int[]sz) { int ...

  3. Redis内存模型(2):存储细节

    1. 概述 先看一下执行set hellow world时,所涉及的数据模型: (1)dictEntry:Redis是Key-Value数据库,因此对每个键值对都会有一个dictEntry,里面存储了 ...

  4. 配置IIS网站,我遇到的那些坑~

    配置错误 不能在此路径中使用此配置节.如果在父级别上锁定了该节,便会出现这种情况.锁定是默认设置的(overrideModeDefault="Deny"),或者是通过包含 over ...

  5. 函数声明 和 var声明的优先级

    function demo() { console.log(5) } var demo = function(){ console.log(4) } console.log(demo()) var d ...

  6. Luogu P5279 [ZJOI2019]麻将

    ZJOI2019神题,间接送我退役的神题233 考场上由于T2写挂去写爆搜的时候已经没多少时间了,所以就写挂了233 这里不多废话直接开始讲正解吧,我们把算法分成两部分 1.建一个"胡牌自动 ...

  7. EL表达式 if 和 if else语句的写法

    JavaScript的if else大家都不会陌生,但可能很多小伙伴并不知道在jsp文件里,el表达式的if else是怎么写的,下面安利给各位小伙伴 el表达式 if 代码示例 <c:if t ...

  8. 【Caffe篇】--Caffe从入门到初始及各层介绍

    一.前述 Caffe,全称Convolutional Architecture for Fast Feature Embedding.是一种常用的深度学习框架,主要应用在视频.图像处理方面的应用上.c ...

  9. TabTopLayout【自定义顶部选项卡区域(固定宽度且居中)】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 自定义顶部选项卡并居中显示.结合显示/隐藏view的方式实现切换功能(正常情况下可能是切换fragment). 效果图 代码分析 T ...

  10. 高效并发unsafe-星耀

    定义 Unsafe类是在sun.misc包下,不属于Java标准.但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty.Cassandra.Hado ...