目前很缺递归思维,主要是算法代码写得少,本篇记录下以 PostgreSQL 代码举例(主要是非常喜欢这款性能小钢炮数据库)。

树状查询不多说,很简单大家基本都会,主要讲 cte 代码递归实现不同需求。

以下所有内容都是我个人理解,不对之处请各位读者多指教!


cte 语法简介

以PG举例,如果是ORACLE的话需要去掉RECURSIVE关键字

WITH RECURSIVE cte_tb (column_list) AS (
-- 开始条件
SELECT ...
UNION ALL    
-- 递归逻辑代码和结束递归逻辑的代码
SELECT ... FROM cte_tb WHERE ...
)
SELECT * FROM cte_tb;

PG使用 cte 递归实现层级查询

scott=> WITH RECURSIVE T(LV, EMPNO, ENAME, MGR) AS (
scott(> SELECT 1 AS LV, EMPNO, ENAME, MGR FROM EMP WHERE MGR IS NULL -- 根节点,(开始条件)
scott(> UNION ALL
scott(> SELECT T.LV + 1, E.EMPNO, E.ENAME, E.MGR FROM EMP E
scott(> INNER JOIN T ON T.EMPNO = E.MGR -- e表属于子节点,t表属于上层节点 t.EMPNO = e.MGR 相当于 prior empno = mgr; (递归条件),如果 t.EMPNO = e.MGR 匹配不上了就返回NULL (递归结束条件)
scott(> )
scott-> SELECT *
scott-> FROM T;
lv | empno | ename | mgr
----+-------+--------+------
1 | 7839 | KING |
2 | 7566 | JONES | 7839
2 | 7698 | BLAKE | 7839
2 | 7782 | CLARK | 7839
3 | 7499 | ALLEN | 7698
3 | 7521 | WARD | 7698
3 | 7654 | MARTIN | 7698
3 | 7788 | SCOTT | 7566
3 | 7844 | TURNER | 7698
3 | 7900 | JAMES | 7698
3 | 7902 | FORD | 7566
3 | 7934 | MILLER | 7782
4 | 7369 | SMITH | 7902
4 | 7876 | ADAMS | 7788
(14 rows) Time: 0.396 ms

CTE 递归核心思想

一、使用 cte 递归,一定要满足以下三个条件:  

  1. 开始条件。  
  2. 递归条件。  
  3. 递归结束条件。

二、递归重要的思想:

  1. 大问题拆小问题,这个比较难,(怎么拆、小问题之间的逻辑如何关联上,递归结束条件如何满足)等, 这也主要是我缺乏递归思维原因。  
  2. 递归和循环的思路是高度相似:      
    1. 循环需要 开始条件、结束条件、循环逻辑。    
    2. 递归需要 开始条件、结束条件、递归逻辑+调用自身逻辑。

案例一、cte 递归实现数字递增:

with RECURSIVE x(seq) as (
SELECT 1 as seq -- SELECT 1 as seq from DUAL 递归开始条件
UNION ALL
SELECT x.seq + 1 as seq from x -- x.seq + 1 from x 递归条件(每次执行 + 1 ) 调用自身
WHERE x.seq < 10 -- x.seq < 10 递归结束条件
)
SELECT * FROM x ORDER BY 1; seq
-----
1
2
3
4
5
6
7
8
9
10
(10 rows) Time: 0.700 ms

上面这个案例很像循环,但是总体实现起来整体的思路会比循环稍微复杂那么一丢丢。

其实在 PG 来说实现数字递增的方式很多,例如:序列、SERIAL 、PLPG/SQL for 循环, 均能实现类似效果,上面案例案例让各位读者初步感受下。


案例二、cte 递归实现distinct效果

distinct sql

select distinct col from tt2;

  col
--------
C
JAVA
PL/SQL
Python
(4 rows) Time: 255.794 ms

使用CTE递归的方式实现

 WITH RECURSIVE t(col) as (

     (SELECT col from tt2 ORDER BY col LIMIT 1)                                      --   递归开始条件。
UNION ALL
SELECT (SELECT col FROM tt2 WHERE tt2.COL > t.COL order by tt2.COL LIMIT 1) -- tt2.COL > t.COL 大问题拆小问题 ,递归逻辑
FROM t WHERE t.COL IS NOT NULL -- 递归结束条件
)
SELECT * FROM t WHERE t.COL is not NULL ; col
--------
C
JAVA
PL/SQL
Python
(4 rows) Time: 0.871 ms

这个案例引用的是德哥的思路,PG 15 上对 distinct 算子优化过(支持并行),一千万行数据 265 ms 就能跑出结果。

但是如果使用 cte 递归的话,根本不需要并行,0.8 ms 便能出结果,秒杀优化器算法。

这个 order by tt2.col 非常牛逼,神来之笔,相当于进一步优化了整个递归的算法模型。

基于德哥的思路做了修改

 WITH RECURSIVE t(col) as (

     (SELECT col from tt2 ORDER BY col LIMIT 1)
UNION ALL
SELECT (SELECT col FROM tt2 WHERE tt2.COL > t.COL GROUP BY tt2.COL LIMIT 1) FROM t WHERE t.COL IS NOT NULL
)
SELECT * FROM t WHERE t.COL is not NULL ; col
--------
C
JAVA
PL/SQL
Python
(4 rows) Time: 0.432 ms

order by 改成 group by 是借鉴德哥思路,我自己想的改良版,速度提升了 0.4ms , 不过总体来说差不多,有真实案例看场景使用。


案例三、cte 递归实现阶乘算法:

 WITH  RECURSIVE  factorial (n, factorial_val) AS (
(SELECT 1 n, 1 factorial_val ) -- 递归开始条件 : 1的阶乘为1
UNION ALL
SELECT f.n + 1, (f.n + 1) * f.factorial_val /* 递归逻辑 (1 + 1) * 1 = 2
(2 + 1) * 2 = 6
(3 + 1) * 6 = 24
(4 + 1) * 24 = 120
*/ FROM factorial f
WHERE f.n < 5 -- 结束递归条件,算 5 的阶乘
)
SELECT max(factorial_val) FROM factorial; max
-----
120
(1 row) Time: 0.395 ms

CTE 递归也能实现阶乘的逻辑,由于 PG 上是没阶乘函数的,可以将上面逻辑封装到一个函数里面进行使用,代码如下:

CREATE OR REPLACE FUNCTION factorial(num BIGINT)
RETURNS BIGINT AS $$
DECLARE
result BIGINT;
BEGIN
WITH RECURSIVE factorial (n, factorial_val) AS (
(SELECT 1::INT as n , 1::int as factorial_val)
UNION ALL
SELECT f.n + 1, (f.n + 1) * f.factorial_val
FROM factorial f
WHERE f.n < num
)
SELECT max(factorial_val) INTO result FROM factorial; RETURN result;
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT;

结束语

cte 递归的技巧在任何数据库都通用,我这里只是使用了PG作为演示案例,递归不仅仅是树状查询,理论上来说,只要能拆解逻辑(这也是最难的),所有SQL逻辑都能使用递归来表达。

但是这玩意是个双刃剑,不是所有场景都能使用,假如一个列的选择性很高,例如主键,如果使用递归来进行匹配查找的话,那绝对是个非常不明智的选择,线性递归的时间复杂度是O(n),速度取决于你的数据量。

没有最好的算法,只有最合适的算法。不过有递归思维的话,确实能解决很多日常和工作中不同类型的事物。

SQL 递归核心思想(递归思维)的更多相关文章

  1. 读《SQL优化核心思想》:你不知道的优化技巧

    SQL性能问题已经逐步发展成为数据库性能的首要问题,80%的数据库性能问题都是因SQL而导致. 1.1 基数(CARDINALITY) 某个列唯一键(Distinct_Keys)的数量叫作基数.比如性 ...

  2. 【吐血分享】SQL Server With As 递归获取层级关系数据

    纯洁的一周又开始了,今天看到一则新闻,笑尿了,和袁友们一起娱乐下 最近两月在做基于Saas模式的人力资源管理产品,平常数据库设计我经常会遇到如下需求场景: 以前商城类网站在设计类型表的时候,设计成单表 ...

  3. 洗礼灵魂,修炼python(26)--编程核心之“递归”

    递归 1.什么是递归: 其实前面都提过,但没有详细讲.多次调用自身就叫递归 看图,这种就叫递归 看过盗梦空间没?其实也是递归 2.递归需要满足条件: 有调用函数自身 有一个正确的返回条件来结束 在使用 ...

  4. 递归-归并排序 思想 JAVA实现

    已知一个数组   15.58.61.75.21.32.89.4.78.83.采用递归实现的归并排序将数组有序. 分治策略:(摘自<算法导论>) 在分治策略中,我们采用递归解决问题 分解:将 ...

  5. 在论坛中出现的比较难的sql问题:34(递归 获取连续值问题)

    原文:在论坛中出现的比较难的sql问题:34(递归 获取连续值问题) 所以,觉得有必要记录下来,这样以后再次碰到这类问题,也能从中获取解答的思路.

  6. 在论坛中出现的比较难的sql问题:33(递归 连续日期问题 )

    原文:在论坛中出现的比较难的sql问题:33(递归 连续日期问题 ) 最近,在论坛中,遇到了不少比较难的sql问题,虽然自己都能解决,但发现过几天后,就记不起来了,也忘记解决的方法了. 所以,觉得有必 ...

  7. 在论坛中出现的比较难的sql问题:21(递归问题 检索某个节点下所有叶子节点)

    原文:在论坛中出现的比较难的sql问题:21(递归问题 检索某个节点下所有叶子节点) 最近,在论坛中,遇到了不少比较难的sql问题,虽然自己都能解决,但发现过几天后,就记不起来了,也忘记解决的方法了. ...

  8. 在论坛中出现的比较难的sql问题:12(递归问题2 拆分字符串)

    原文:在论坛中出现的比较难的sql问题:12(递归问题2 拆分字符串) 最近,在论坛中,遇到了不少比较难的sql问题,虽然自己都能解决,但发现过几天后,就记不起来了,也忘记解决的方法了. 所以,觉得有 ...

  9. 在论坛中出现的比较难的sql问题:8(递归问题 树形结构分组)

    原文:在论坛中出现的比较难的sql问题:8(递归问题 树形结构分组) 最近,在论坛中,遇到了不少比较难的sql问题,虽然自己都能解决,但发现过几天后,就记不起来了,也忘记解决的方法了. 所以,觉得有必 ...

  10. 《深入理解Spark:核心思想与源码分析》(第2章)

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

随机推荐

  1. 【架构师视角系列】QConfig配置中心系列之Client端(二)

    目录 声明 配置中心系列文章 一.架构 一.客户端架 1.Server 职责 (1)配置管理 (2)配置发布 (3)配置读取 2.Client 职责 (1)配置拉取 (2)配置注入 (3)配置变更监听 ...

  2. portainer docker可视化工具

    下载可视化工具 docker pull portainer/portainer 启动portainer --restart=always 只要挂掉了 就自动重启 docker run -d -p 80 ...

  3. 【Azure 云服务】Azure Cloud Service中的错误事件 Error Event(Defrag/Perflib) 解答

    问题描述 在Azure Cloud Service的实例中,收集到各种 Error Event 内容,本文针对所收集的三种Event进行解析. 1: This operation is not sup ...

  4. 没想到,JDBC 驱动会偷偷修改 sql_mode 的会话值

    最近碰到一个 case,值得分享一下. 现象就是一个 update 操作,在 mysql 客户端中执行提示 warning,但在 java 程序中执行却又报错. 问题重现 mysql> crea ...

  5. CDC作业历史记录无法删除问题

    背景 数据库开启CDC功能后,每天会生成大量的历史记录,即使达到参数"每个作业的最大历史记录"的阈值后也不会被删除,导致其它作业的历史记录被删除,无法查看以前的执行情况,非常不方便 ...

  6. 树形dp套路

    我们知道dp也就是动态规划的思想就是先解决小问题,通过不断的解决小问题,最终解决大问题.那么能够应用树形dp套路的题目都应该符合一个条件,那就是通过解决每个子树的小问题,最终解决整棵树的大问题. 套路 ...

  7. 摆脱鼠标系列 - vscode - ctrl+up 光标上移动4行 ctrl+down 光标下移4行

    为什么 摆脱鼠标系列 - vscode - ctrl+up 光标上移动4行 之前滚动屏幕总是用鼠标,现在改为 ctrl + 上箭头或下箭头 实现起来稍微有些麻烦 实现 需要安装 macros 插件 这 ...

  8. Mysql导出导入操作

    安装mysql客户端 # 在终端上下载mysql源 wget https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm ...

  9. vue入门教程之-插槽

    vue入门教程之-插槽 欢迎关注博主公众号「java大师」, 专注于分享Java领域干货文章, 关注回复「资源」, 免费领取全网最热的Java架构师学习PDF, 转载请注明出处 https://www ...

  10. Base MYSQL Database create stored procedures resolve the Delimiter error

    Base MYSQL Database create stored procedures resolve the Delimiter error, It must be created using a ...