首页 > 数据库 >SQL 递归核心思想(递归思维)

SQL 递归核心思想(递归思维)

时间:2024-04-05 19:25:16浏览次数:27  
标签:核心思想 递归 cte factorial -- SQL col SELECT

目前很缺递归思维,主要是算法代码写得少,本篇记录下最近思考的内容。以 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),速度取决于你的数据量。

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

标签:核心思想,递归,cte,factorial,--,SQL,col,SELECT
From: https://www.cnblogs.com/yuzhijian/p/18115913

相关文章

  • 使用pip install mysqlclient命令安装mysqlclient失败?
    写在前面我们使用Django、flask等来操作MySQL,实际上底层还是通过Python来操作的。因此我们想要用Django来操作MySQL,首先还是需要安装一个驱动程序。在Python3中,驱动程序有多种选择。比如有pymysql以及mysqlclient等。常见的Mysql驱动介绍:MySQL-python:也就是MySQLdb。是对C语言操......
  • MySQL-相关约束
    MySQL-约束前提:防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息。为了保证数据的完整性,SQL规范以约束的方式对表数据进行额外的条件限制。有以下考虑要点:①实体完整性(EntityIntegrity):例如,同一个表中,不能存在两条完全相同无法区......
  • MySQL 主从复制
    概述在主从复制中,一般有一个主数据库(Master)和一个或多个从数据库(Slave),主数据库负责接收和处理写操作,从数据库复制主数据库的日志文件,将写操作在自身的数据库重演,从而实现数据的同步复制类型STATEMENT:把主数据库执行的sql复制到从数据库,是默认类型ROW:直接把数据行复制过去......
  • 算法分析与设计——实验1: 递归与分治
    实验一 递归与分治一、实验目的        1、理解分治算法的概念和基本要素;        2、理解递归的概念;        3、掌握设计有效算法的分治策略。二、实验内容和要求实验要求:通过上机实验进行算法实现,保存和打印出程序的运行结果,并结合程序进行......
  • 【附源码】计算机毕业设计招投标管理系统(java+springboot+mysql+mybatis+论文)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义在建筑、工程及众多行业领域,招投标活动是获取项目和签订合同的关键环节。一个高效的招投标管理系统能够帮助企业规范招投标流程,提高文档处理效率,确保信息透明公正,......
  • 【附源码】计算机毕业设计在线音乐播放平台(java+springboot+mysql+mybatis+论文)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义在线音乐播放平台随着互联网技术的发展和数字媒体的普及逐渐成为人们获取音乐的主要途径。这类平台不仅为用户提供了便捷的音乐收听体验,还推动了音乐产业的新商业......
  • 函数递归
    调用自身的编程技巧称为递归把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解把大事化小1,举例2,递归打印数字递归的两个必要条件存在限制条件,当满足这个条件时,递归便不在继续每次递归调用之后越来越接近这个限制条件3,递归模拟strlen函数  ......
  • Mysql的事务
    MySQL的事务(Transaction)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务能把数据库从一个一致性状态转变为另一个一致性状态。1事务得特性A/Atomicity:原子性C/Consistency:......
  • 关于MySQL数据库的几个简单的入门代码注释
    不是很全,是我刚开始学习数据库时记的笔记%FOUND判断游标有效性%ROWTYPE行数据类型%属性:=赋值符号1ISTABLEOF21、2类型一样ABS系统自带函数绝对值ALL()比所有都ANY()任意一个(some用法意思一样)AS命别名,连接ASC升序AVG()函数求平均数BEGIN执行部分BULKCOLLECT......
  • 第三章-常用的渗透测试工具-(sqlmap)
    常用渗透测试工具1.sqlmap支持的数据库:MySQL、Oracle、PostgreSQL、SQLServer、Access、IBMDB2、SQLite、Firebird、Sybase、SAPMaxDB支持的六种注入技术:boolean-based盲注、time-based盲注、error-based、UNION查询、堆叠查询和带外查询B:Boolean-basedblindSQLinjectio......