热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

postgresql查询子孙数据_PostgreSQL通过WITH查询(公用表表达式,CTE)遍历树结构

需求场景行政区划,机构、人员组织结构等信息,天然符合树状结构,在数据库表中通常会以ID和上级ID来存储其上下级隶属关系。业务场景则常常会需

需求场景

行政区划,机构、人员组织结构等信息,天然符合树状结构,在数据库表中通常会以ID和上级ID来存储其上下级隶属关系。业务场景则常常会需要对这类表”顺藤摸瓜“,通过一个结点来查询其所有祖先或者子孙结点,比如通过某人逐级查询出其所有直接、间接的上级领导,或直接、间接的下级员工。传统SQL对此类需求只能通过存储过程或外部程序迭代发出多个查询来完成,如果应用环境只允许用户运用单一的SQL语句,则用户将无法完成遍历树的查询任务。SQL:1999标准推出了公用表表达式(Common Table Expression, CTE)的概念,可由WITH查询定义CTE,其中递归(Recursive)形式的CTE可用来遍历树结构。下面我们以PostgreSQL(从8.4版本开始支持CTE)为平台,来讲解如何用CTE做树结构遍历。

WITH查询(公用表表达式)

WITH为较大的查询提供了书写辅助语句的手段,辅助语句经常以公用表表达式(Common Table Expression, 缩写为CTE)形式被引用。WITH可将多条辅助语句附着在一条主语句上,辅助语句可以是SELECT, INSERT, UPDATE, DELETE,即增删改查,主语句也可以是增删改查。

示例1:

WITH cte1 AS (SELECT ...FROM xxx...
),cte2(x,y,z) AS (SELECT ...FROM cte1...
)
SELECT ...
FROM cte1 t1
INNER JOIN cte2 t2
ON
...
;

示例1中建立了两个公用表表达式:cte1和cte2,其中cte2中还引用了cte1,即把cte1的结果当作FROM的来源,而主查询则同时引用了cte1和cte2,并用其做连接。其中cte2在表名后重新对位定义了查询结果的列名分别为x, y, z,而cte1的列名则依赖于其内部查询。可以认为CTE是为单个查询定义的临时表,它不存储为实际的数据表,只在查询期间有效,并且可以自引用,可以被多次引用。

递归的WITH查询

如需要使用可自引用的CTE,则要在WITH后加入RECURSIVE修饰符。

示例2:

WITH RECURSIVE
cte1 AS (SELECT 1 AS nUNION ALLSELECT n&#43;1 AS nFROM cte1WHERE n<3
)
SELECT *
FROM cte1;

示例2中&#xff0c;查询代码由多个SELECT经由UNION&#xff08;或UNION ALL&#xff09;连接而成&#xff0c;其中最后一个UNION&#xff08;或UNION ALL&#xff09;之后的SELECT为递归项&#xff0c;可以使用自引用&#xff08;即可以在其中使用cte1&#xff09;&#xff0c;而在其之前不管经由几个UNION&#xff08;或UNION ALL&#xff09;合并&#xff0c;均为非递归项&#xff0c;不能使用自引用。RECURSIVE是必须要写的&#xff0c;不然系统会报错。对于没有做自引用的查询&#xff0c;依然可以用WITH RECURSIVE做引导。递归项通过CTE名称实现多轮的迭代自引用&#xff0c;每一轮引用上一轮的结果&#xff0c;第1轮引用非递归项的结果。虽然实现方式其实是迭代&#xff0c;但是SQL标准委员会选择使用“递归”作为术语。

按照PostgreSQL官方文档的描述&#xff0c;递归CTE的逻辑执行流程大概如下图&#xff1a;

0adad4d48f3306187e3de5ddf74f0224.png
递归CTE的逻辑执行流程

结合上图的逻辑流程&#xff0c;以示例2为例&#xff0c;其执行情况见下图&#xff1a;

abd2922537d5cd778a1ed99d435a802c.png
示例2递归CTE的逻辑执行流程

遍历特定的行政区划树

既然可以多次引用上轮查询的结果&#xff0c;那么迭代遍历树也可得以实现。以行政区划为例&#xff0c;先建立行政区划信息表&#xff1a;

CREATE TABLE IF NOT EXISTS ad_info(ad_code VARCHAR(6) PRIMARY KEY,parent_ad_code varchar(6),ad_level smallint,ad_name text
);
/*县级以上行政区划代码信息可在民政部网站下载*/
INSERT INTO ad_info(ad_code,ad_level,ad_name,parent_ad_code)
VALUES
(&#39;100000&#39;,0,&#39;中华人民共和国&#39;,NULL),
(&#39;110000&#39;,1,&#39;北京市&#39;,&#39;100000&#39;),
(&#39;110100&#39;,2,&#39;北京市辖区&#39;,&#39;110000&#39;),
(&#39;110101&#39;,3,&#39;东城区&#39;,&#39;110100&#39;),...(&#39;659004&#39;,3,&#39;五家渠市&#39;,&#39;659000&#39;),
(&#39;710000&#39;,1,&#39;台湾省&#39;,&#39;100000&#39;),
(&#39;810000&#39;,1,&#39;香港特别行政区&#39;,&#39;100000&#39;),
(&#39;820000&#39;,1,&#39;澳门特别行政区&#39;,&#39;100000&#39;);

ad_code为行政区划代码。parent_ad_code为上级代码&#xff0c;如无上级则取NULL。

现给定行政区划代码&#xff08;如“沈阳市”&#xff09;&#xff0c;要查询其本身&#xff08;以“C”标识&#xff09;&#xff0c;以及其所有上级&#xff08;以“A”标识&#xff09;和所有下级&#xff08;以“S”标识&#xff09;行政区划信息。查询代码如示例3&#xff1a;

示例3&#xff1a;

WITH RECURSIVE
cte1(ad_code,ad_level,ad_name,parent_ad_code,ad_type
) AS (SELECTad_code,ad_level,ad_name,parent_ad_code,cast(&#39;C&#39; as varchar(1))FROM ad_infoWHERE ad_code&#61;&#39;210100&#39;UNION ALLSELECTt1.ad_code,t1.ad_level,t1.ad_name,t1.parent_ad_code,cast(&#39;A&#39; as varchar(1))FROM ad_info t1INNER JOIN cte1ON t1.ad_code&#61;cte1.parent_ad_code
),
cte2 AS (SELECTad_code,ad_level,ad_name,parent_ad_code,cast(&#39;S&#39; as varchar(1)) AS ad_typeFROM ad_infoWHERE parent_ad_code&#61;&#39;210100&#39;UNION ALLSELECTt1.ad_code,t1.ad_level,t1.ad_name,t1.parent_ad_code,cast(&#39;S&#39; as varchar(1)) AS ad_typeFROM ad_info t1INNER JOIN cte2ON t1.parent_ad_code&#61;cte2.ad_code
)
SELECT * FROM cte1
UNION ALL
SELECT * FROM cte2
ORDER BY ad_type,ad_level,ad_code
;

主查询只是简单的从cte1和cte2中取全部数据并在合并后做了排序。

cte1的非递归项负责查询指定行政区划代码的信息&#xff0c;递归项通过内连接&#xff0c;由非递归项出发逐级查询每个行政区划代码的上级信息&#xff0c;当上级代码为NULL时&#xff0c;由于任何行政区划的代码都不为NULL&#xff0c;因此内连接结果为0行的空表&#xff0c;迭代查询结束。

cte2的非递归项负责查询指定行政区划代码的下级行政区划&#xff0c;递归项通过内连接&#xff0c;由非递归项出发逐级查询指定行政区划的历级下级行政区划信息&#xff0c;当某次查询提供的行政区划代码通过内连接连不到任何上级代码时&#xff0c;内连接结果为0行的空表&#xff0c;迭代查询结束。

查询结果&#xff1a;

34549e2da747163dbcbaafcd2191189f.png

PostgreSQL官方文档参见&#xff1a;

WITH Queries (Common Table Expressions)​www.postgresql.org


推荐阅读
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 本文讨论了在数据库打开和关闭状态下,重新命名或移动数据文件和日志文件的情况。针对性能和维护原因,需要将数据库文件移动到不同的磁盘上或重新分配到新的磁盘上的情况,以及在操作系统级别移动或重命名数据文件但未在数据库层进行重命名导致报错的情况。通过三个方面进行讨论。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文由编程笔记小编整理,介绍了PHP中的MySQL函数库及其常用函数,包括mysql_connect、mysql_error、mysql_select_db、mysql_query、mysql_affected_row、mysql_close等。希望对读者有一定的参考价值。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
author-avatar
wangyongjieyexuying677
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有