五种主流数据库:递归查询

通用表表达式(WITH 语句)能够将复杂的查询语句模块化,实现结果集的重复使用,提高 SQL 语句的可读性和性能。同时,递归形式的 CTE 提供了遍历层次数据和分析网络图数据的强大功能。

本文比较了五种主流数据库中的递归查询功能,包括 MySQL、Oracle、SQL Server、PostgreSQL 以及 SQLite。

功能MySQLOracleSQL ServerPostgreSQLSQLite
简单CTE✔️✔️✔️✔️✔️
递归CTE✔️✔️✔️✔️✔️

表即变量

在使用编程语言时,我们通常会定义一些变量和函数(方法)。变量可以被重复使用;函数(方法)可以将代码模块化,从而提高程序的可读性与可维护性。

与此类似,SQL 中的通用表表达式也能够实现查询结果的重复利用,简化复杂的连接查询和子查询。SQL 通用表表达式的基本语法如下:

WITH cte_name(col1, col2, ...) AS (
 subquery
)
SELECT * FROM cte_name;

其中,WITH 关键字表示定义通用表表达式(简称 CTE),因此通用表表达式也被称为WITH 查询。cte_name 指定了 CTE 的名称,后面是可选的字段名。AS 关键字后面的子查询是CTE 的定义语句,定义了它的表结构和数据。最后的 SELECT 是主查询语句,它可以引用前面
定义的 CTE。除了 SELECT 之外,主查询语句也可以是 INSERT、UPDAT 或 DELETE 等。

例如,以下是一个简单的 CTE 示例:

WITH t(n) AS (
 SELECT 1
)
SELECT n
FROM t;

其中,WITH 关键字表示定义 CTE。t 就是一个 CTE,包含一个字段 n 并且只有一行数据。最后,我们在主查询语句中使用了前面定义的 t。查询返回的结果如下:

n
-
1

提示:WITH 语句定义了一个变量,这个变量的值是一个表,所以称为通用表表达式。CTE 和临时表或者子查询类似,可以用于 SELECT、INSERT、UPDATE 以及 DELETE 等语句。在 Oracle 数据库中 WITH 语句被称为子查询因子。

CTE 与子查询类似,只在当前语句中有效。我们在一个 WITH 子句中可以定义多个 CTE,并且已经定义的 CTE 可以被后续的 CTE 引用。例如,以下示例中定义了 2 个 CTE:

WITH t1(n) AS (
 SELECT 1
),
t2(m) AS (
 SELECT n+1
 FROM t1
)
SELECT t1.n, t2.m
FROM t1
CROSS JOIN t2;

其中,WITH 关键字表示定义 CTE。t1 包含一个字段 n 并且只有一行数据。t2 包含一个字段 m,同时在 t2 的定义中引用了前面的 t1。两个 CTE 之间使用逗号进行分隔。最后,我们在主查询语句中通过 t1 和 t2 的交叉连接返回了两个表中的数据。查询返回的结果如下:

n|m
-|-
1|2

我们再来看一个示例,以下语句中使用了子查询:

SELECT dept_cost.dept_name AS "部门名称", 
       dept_cost.total AS "部门成本", 
        detp_avg_cost.avg_total AS "平均成本"
FROM (SELECT dept_name, SUM(salary*12 + COALESCE(bonus, 0)) total
      FROM employee e
      JOIN department d ON (e.dept_id = d.dept_id)
      GROUP BY dept_name
 ) dept_cost
JOIN (SELECT SUM(total)/COUNT(*) avg_total
      FROM (SELECT dept_name, SUM(salary*12 + COALESCE(bonus, 0)) total
            FROM employee e
            JOIN department d
            ON (e.dept_id = d.dept_id)
            GROUP BY dept_name
           ) dept_cost
     ) detp_avg_cost
ON (dept_cost.total > detp_avg_cost.avg_total);

其中,FROM 子句中的子查询 dept_cost 返回了每个部门的总体成本。我们在 JOIN 子句中再次定义了相同的子查询 dept_cost,并且基于该子查询定义了子查询 detp_avg_cost,得到了所有部门的平均成本。最后,我们在外部查询中返回了部门成本大于平均成本的部门。查询返回的结果如下:

部门名称 |部门成本 |平均成本
--------|---------|-------------
行政管理部|990000.00|601420.000000
研发部 |824400.00|601420.000000

通过以上示例可以看出,当逻辑稍微复杂一些时,使用子查询编写的语句不易阅读和理解,同时也为性能优化增加了难度。

我们使用 CTE 将上面的示例进行改写:

WITH dept_cost(dept_name, total) AS 
 (SELECT dept_name, SUM(salary*12 + COALESCE(bonus, 0))
 FROM employee e
 JOIN department d ON (e.dept_id = d.dept_id)
 GROUP BY dept_name
),
detp_avg_cost(avg_total) AS (
 SELECT SUM(total)/COUNT(*) avg_total
 FROM dept_cost
)
SELECT dept_cost.dept_name AS "部门名称", 
 dept_cost.total AS "总成本", 
 detp_avg_cost.avg_total AS "平均成本"
FROM dept_cost
JOIN detp_avg_cost
ON (dept_cost.total > detp_avg_cost.avg_total);

其中,dept_cost 是一个 CTE,包含了每个部门的名称和成本。detp_avg_cost 也是一个 CTE,引用了前面定义的 dept_cost,包含了所有部门的平均成本。最后,我们在主查询语句,通过这两个 CTE 的连接查询返回了部门成本大于平均成本的部门。显然,使用 CTE 编写的查询语句更易理解,且更加高效。

提示:通用表表达式(WITH 语句)可以将 SQL 语句进行模块化和重复利用,从而提高复杂查询语句的可读性和性能。

强大的递归

除提高查询的可读性和性能外,CTE 还支持在定义中进行自我引用,也就是实现了编程语言中的递归调用。递归形式的通用表表达式可以用于遍历具有层次结构或者树状结构的数据,例如遍历组织结构、查询地铁线路图。

递归查询语法

递归 CTE 的基本语法如下:

WITH RECURSIVE cte_name AS(
 cte_query_initial -- 初始化部分
 UNION [ALL]
 cte_query_iterative -- 递归部分
) SELECT * FROM cte_name;

其中,关键字 WITH RECURSIVE 表示定义递归形式的 CTE(即递归 CTE)。递归 CTE 的定义包含两部分, cte_query_initial 是初始化查询语句,用于创建初始结果集 。cte_query_iterative 是递归查询语句,可以对当前 CTE 进行自我引用。每一次递归查询语句执行的结果都会再次作为输入,传递给下一次查询。如果递归查询无法从上一次迭代中返回更多的数据,将会终止递归。最后,UNION [ALL] 运算符用于合并这两个结果集。

提示:Oracle 和 Microsoft SQL Server 不支持 RECURSIVE,而直接使用 WITH 定义递归形式的 CTE,同时它们必须使用 UNION ALL 运算符。SQLite 可以省略 RECURSIVE。

接下来我们介绍一些递归 CTE 的使用示例。

生成数字序列

以下查询通过递归 CTE 生成一个 1~10 的数字序列:

-- MySQL、PostgreSQL 以及 SQLite
WITH RECURSIVE t(n) AS
(
 SELECT 1
 UNION ALL
 SELECT n + 1 FROM t WHERE n < 10
)
SELECT n FROM t;

-- Oracle、Microsoft SQL Server
WITH t(n) AS
(
 SELECT 1
 UNION ALL
 SELECT n + 1 FROM t WHERE n < 10
)
SELECT n FROM t;

其中,WITH 或者 WITH RECURSIVE 表示定义递归形式的 CTE。该语句的执行过程如下:

  1. 首先运行初始化语句,生成数字 1。
  2. 第 1 次运行递归部分,此时 n 等于 1,生成数字 2(n+1)。
  3. 第 2 次运行递归部分,此时 n 等于 2,生成数字 3。
  4. 继续运行递归部分,直到 n 等于 9,生成数字 10。
  5. 第 10 次运行递归部分,此时 n 等于 10。由于不满足查询条件(WHERE n < 10),不返回任何结果,同时终止递归。
  6. 最后,主查询语句返回 t 中的全部数据,也就是一个 1~10 的数字序列。

查询返回的结果如下:

n
--
1
2
3
...
9
10

从该示例中可以看出,递归 CTE 非常合适用来生成具有某种规律的数字序列,例如斐波那
契数列(Fibonacci Series)等。

遍历层次结构

员工表(employee)中存储了员工的各种信息,包括员工编号、姓名以及员工经理的编号。其中“刘备”没有上级,他的经理字段 manager 为空。

以下查询利用递归 CTE 生成一个组织结构图,显示每个员工从上到下的管理路径:

-- MySQL
WITH RECURSIVE employee_path (emp_id, emp_name, path) AS
(
 SELECT emp_id, emp_name, emp_name AS path
 FROM employee
 WHERE manager IS NULL
 UNION ALL
 SELECT e.emp_id, e.emp_name,
 CONCAT(ep.path, '->', e.emp_name)
 FROM employee_path ep
 JOIN employee e ON ep.emp_id = e.manager
)
SELECT emp_name AS "员工姓名", PATH AS "管理路径"
FROM employee_path 
ORDER BY emp_id;

其中,employee_path 是一个递归 CTE。UNION ALL 前面的初始化部分用于查找上级经理为空的员工,也就是“刘备”。path 字段用于保存从上到下的管理路径。然后第一次执行递归部分,将初始化的结果集(employee_path)与员工表进行连接查询,并找出“刘备”的所有直接下属。CONCAT 函数用于将之前的管理路径加上当前员工的姓名,生成新的管理路径。第一次执行递归部分之后的结果如下:

员工姓名|管理路径 
-------|----------
刘备   |刘备 
关羽   |刘备->关羽
张飞   |刘备->张飞
诸葛亮 |刘备->诸葛亮
孙尚香 |刘备->孙尚香
赵云   |刘备->赵云

查询继续执行递归部分,不断返回其他员工的下级员工,直到不再返回新的员工为止。查询最终返回的查询结果如下:

员工姓名|管理路径 
-------|-------------------
刘备   |刘备 
关羽   |刘备->关羽 
张飞   |刘备->张飞 
...
简雍   |刘备->关羽->法正->简雍
孙乾   |刘备->关羽->法正->孙乾

如果使用 Oracle 数据库,我们需要删除 RECURSIVE 关键字,并且将 CONCAT 函数替换为连接运算符(||)。

如果使用 Microsoft SQL Server,我们需要将查询语句修改如下:

-- Microsoft SQL Server
WITH employee_path (emp_id, emp_name, path) AS
(
 SELECT emp_id, emp_name, CAST(emp_name AS VARCHAR) AS path
 FROM employee
 WHERE manager IS NULL
 UNION ALL
 SELECT e.emp_id, e.emp_name,
 CAST(CONCAT(ep.path, '->', e.emp_name) AS VARCHAR)
 FROM employee_path ep
 JOIN employee e ON ep.emp_id = e.manager
)
SELECT emp_name AS "员工姓名", PATH AS "管理路径"
FROM employee_path;

首先,我们删除了 RECURSIVE 关键字。其次,我们使用 CAST 函数将 path 字段的类型转换为 VARCHAR 类型,否则查询会返回数据类型不匹配的错误。

如果使用 PostgreSQL,我们需要使用 CAST 函数将 path 字段的类型转换为 VARCHAR(n) 或者 TEXT 类型,否则查询会返回数据类型不匹配的错误。

如果使用 SQLite,我们需要将 CONCAT 函数替换为连接运算符(||)。

递归的终止

一般而言,递归 CTE 的定义中需要包含一个终止递归的条件。否则的话,递归将会进入死循环。例如,以下语句删除了生成数字序列示例中的 WHERE 条件:

-- MySQL、PostgreSQL 以及 SQLite
WITH RECURSIVE t(n) AS
(
 SELECT 1
 UNION ALL
 SELECT n + 1 FROM t
)
SELECT n FROM t;

-- Oracle、Microsoft SQL Server
WITH t(n) AS
(
 SELECT 1
 UNION ALL
 SELECT n + 1 FROM t
)
SELECT n FROM t;

如果我们执行以上语句,MySQL 默认递归 1000 次(由系统变量 cte_max_recursion_depth控制)后终止递归,并提示错误。PostgreSQL 和 SQLite 没有进行递归次数限制,查询进入死循环。Oracle 能够检测到查询语句中的死循环问题,并提示错误。Microsoft SQL Server 默认递归100 次(可以在查询中使用 MAXRECURSION 选项进行设置)后终止,并提示错误。

提示:递归终止条件可以是遍历完表中的所有数据后不再返回更多结果,或者在递归查询部分的 WHERE 子句中指定一个终止条件。

另外,限制递归次数的终止条件必须写在 CTE 的定义中,而不能通过主查询实现,例如:

-- MySQL、PostgreSQL 以及 SQLite
WITH RECURSIVE t(n) AS
(
 SELECT 1
 UNION ALL
 SELECT n + 1 FROM t
)
SELECT n FROM t WHERE n < 10;

由于主查询语句中的 WHERE 条件并不会对 CTE 产生影响,因此以上语句仍然会返回错误或者进入死循环。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/580893.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Welcome to nginx!怎么解决?

要解决 "welcome to nginx!" 错误&#xff0c;需要检查虚拟主机配置&#xff0c;启用虚拟主机&#xff0c;重新加载 nginx&#xff0c;如果无法找到虚拟主机配置文件&#xff0c;则创建默认页面并重新加载 nginx&#xff0c;这样错误消息将消失&#xff0c;网站将正常…

数据结构之顺顺顺——顺序表

1.浅谈数据结构 相信我们对数据结构都不陌生&#xff0c;我们之前学过的数组就是最基础的数据结构&#xff0c;它大概就长这样&#xff1a; 数组 而作为最简单的数据结构&#xff0c;数组只能帮助我们实现储存数据这一个功能&#xff0c;随着学习的深入&#xff0c;和问题的日渐…

Qt | 标准、复选、单选、工具、命令按钮大全

01、QPushButton QPushButton 类(标准按钮) 示例 3:默认按钮与自动默认按钮 02、QCheckBox QCheckBox 类(复选按钮) 1、复选按钮的第三状态(见右图 Qt5.10.1 的选中状态):是指除了选中 和未选中状态之外的第三种状态,这种状态用来指示“不变”,表 示用户既不选中也不取…

专栏目录【政安晨的机器学习笔记】

目录 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 本篇是作者政安晨的专栏《政安晨的机器学习笔记》的…

Python学习笔记------模块和包

Python模块 简介与作用 Python模块是一个Python文件&#xff0c;以.py结尾&#xff0c;模块能定义函数、类和变量&#xff0c;模块里也包含可执行的代码 模块的作用&#xff1a;Python中有很多各种不同的模块&#xff0c;每个模块都可以帮我们快速实现一些功能&#xff0c;我…

grafana监控模板 regex截取ip地址

查看prometheus的node服务启动指标up&#xff0c;也可以查看其他的服务 配置监控模板 配置正则截取ip regex截取ip地址 /.*instance"([^"]*):9100*/ #提取&#xff08;instance"&#xff09;开头&#xff0c;&#xff08;:9001&#xff09;结束字段

北京车展“第一枪”:长安汽车发布全球首款量产可变新汽车

4月25日&#xff0c;万众瞩目的2024北京国际汽车展览会在中国国际展览中心如期而至。作为中国乃至全球汽车行业的盛宴&#xff0c;本次车展也吸引了无数业内人士的高度关注。 此次北京车展以“新时代 新汽车”为主题&#xff0c;汇聚了1500余家主流车企及零部件制造商&#xff…

Laravel 6 - 第十七章 配置数据库

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

Kettle 中将图片url转换为Base64

背景 我遇到了一个应用场景需要将订阅kafka数据中的一个字段&#xff08;图片url&#xff09;转换为base64 然后进行下一步操作。 实现方式 我这边的实现方式是使用javaScript去实现的 图形化逻辑如下&#xff1a; 这一步就是实现url转换为base64 json input的步骤&#xf…

vulnhub靶场之driftingblues-6

一.环境搭建 1.靶场描述 get flags difficulty: easy about vm: tested and exported from virtualbox. dhcp and nested vtx/amdv enabled. you can contact me by email for troubleshooting or questions. 2.靶场下载 https://www.vulnhub.com/entry/driftingblues-6,6…

【Spring AI】聊天API-OpenAI-Function Call

文章目录 Function Calling工作原理快速上手将函数注册为 Bean纯 Java 函数实现&#xff08;Plain Java Functions&#xff09;FunctionCallback Wrapper Specifying functions in Chat OptionsRegister/Call Functions with Prompt Options 附录&#xff1a;Spring AI 函数调用…

MySQL使用Sequence创建唯一主键

目录 第一章、快速了解Sequence1.1&#xff09;是什么&#xff1f;为什么使用1.2&#xff09;Sequence和自增主键的区别 第二章、在MySQL中使用Sequence2.1&#xff09;创建mysql_sequence表2.1.1&#xff09;创建表2.1.2&#xff09;插入数据 2.2&#xff09;创建函数2.2.1&am…

Kubernetes学习-核心概念篇(三) 核心概念和专业术语

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Kubernetes渐进式学习-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 1. 前言 在前面两篇文章我们简单介绍了什么是K8S&#xff0c;以及K8S的…

Vue面试经验

Vue部分 Vue编译时声明周期的执行顺序 Vue中父子组件渲染顺序&#xff08;同步引入子组件&#xff1a;import Son from ‘/components/son’ &#xff09; 父子组件编译时的生命周期执行顺序 这里修改data数据时也修改了dom&#xff0c;如过知识通过按钮对数据进行操作&…

MySQL8.0 msi版本安装教程

MySQL8.0 msi 版本安装教程 1> 官网下载安装包 2> 安装MySQL 2.1双击打开下载的安装包&#xff0c;进入到下面这个页面&#xff0c;选择 Custom 选项&#xff0c;之后&#xff0c;点击next 说明&#xff1a; 2.2 选择所需产品&#xff0c;更改安装位置(当然也可以默认安…

springCahe框架

基于springboot项目 介绍:Spring Cache 是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;只需要简单地加一个注解&#xff0c;就能实现缓存功能。 Spring Cache 提供了一层抽象&#xff0c;底层可以切换不同的缓存实现&#xff0c;例如&#xff1a; EHCache Caff…

Java-字符集-Unicode字符集

1 需求 Unicode 字符集UTF-8、UTF-16、UTF-32字符编码 2 接口 3 示例 4 参考资料

新媒体运营-----短视频运营-----PR视频剪辑----软件基础

新媒体运营-----短视频运营-----PR视频剪辑-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/138079659 文章目录 1.1 PR软件重置与初始化设置1.2 新建项目及序列设置1.3 PR工作区的管理方法1.4 导入4K超高清视频并与ME配合工作1…

【C语言】编译与链接

1.翻译环境与运行环境 在ANSI C的任何一种实现中&#xff0c;存在两个不同的环境。 1.翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令&#xff08;二进制指令&#xff09; 2.执行环境&#xff0c;它用于实际执行代码 2.翻译环境 那么翻译环境是怎么将源代码…

Linux(文件系统和日志分析)

目录 1.inode & block​编辑 1.1 inode的内容 1.3 inode的号码 1.4 inode的大小 1.5 inode的特殊作用 1.6 模拟inode号被占满 2. 链接文件 3.文件恢复 3.1 修复EXT类型的文件 3.1.1 EXT类型文件恢复步骤 3.2 修复XFS类型的文件 1.inode & block 1.1 in…