公共表表达式(CTE)-递归查询

递归是编程中的一种常用技术,通常是让函数自我调用,直到达成特定条件,再将结果逐层返回的方法,这里就不展开讲了。

什么是递归查询?

💡 假设现在需要生成从1900年到2024年为止的所有年份数据,每行一年。

像上述这种滚动生成数据的操作在编程中可以使用循环来完成,SQL中没有循环,那么就可以使用递归来完成。

递归查询类似递归函数,都是通过引用自己,再对数据加工处理逐级返回的方式生成数据。

如何使用递归查询?

在SQL中实现递归就需要使用到CTE。什么是CTE?

我们知道在CTE中可以引用声明过的CTE,不仅如此CTE还可以在自己的表达式中引用自己。

以上面提到的生成年份的假设为例,使用上面的SQL即可查询出所有1900到2024的年份

-- 递归查询
WITH TMP (Y) AS (
    -- 初始数据
	VALUES 1900
	
	UNION ALL
	
    -- 递归数据处理
	SELECT Y + 1 FROM TMP 
    -- 生成条件
	WHERE Y < 2024
	
)
SELECT * FROM TMP;

上述SQL使用DB2编写,各数据库系统写法不同,仅作功能展示

初始数据 代表最原始的数据,这里是开始的年份1900

递归数据处理 需要对原始数据施加的操作,这里是生成下一年Y + 1

生成条件 当满足该条件时生成数据,条件不满足后则不再继续生成数据

生成条件是递归中最关键的部分,设置了错误的条件会导致递归没有出口,陷入死循环。

理解递归数据的产生逻辑

其实使用CTE实现递归只需要记住UNION一段带约束条件与字段处理的逻辑即可,多多尝试就可以掌握。

但是这种操作长时间不用很容易忘记,那么我们可以多回来翻阅一下😜

那么我们可以试着理解一下这个逻辑来加深印象。


在这段递归逻辑中,包含了三个结构:CTE整体锚定查询递归查询

锚定查询递归查询构成了CTE整体,这是非常正常的逻辑,特殊的地方就在于递归查询中引用了CTE整体

不容易理解的点就在于局部的加工需要引用到局部所在的整体。就像是我拉着我自己的衣服把我自己提起来一样奇怪。

由此引出了一些问题:局部是怎么引用到整体的?引用到的整体是什么样的?

局部怎么引用到整体

我虽然不能把自己提起来,但是程序却是可以做到的。严格意义上来讲程序也不是把“自己”提起来了,它只是抓到了自己的克隆体。

回到上面的例子。虽然在递归查询的部分引用了CTE整体,但是引用的CTE整体并不是计算完成的状态,只是锚定查询部分的结果以及递归查询组成的临时CTE

引用到的整体是什么

实际上引用到的整体就是新的临时CTE,其中各部分的功能如下:

  • 锚定查询的结果集,用来参与递归查询部分的计算,并返回新的结果集,也就是需要扩增数据。比如例子中的VALUES 1900 查询得到的结果装入临时CTE后,通过Y+1的计算后得到了新的结果集1901 ,我们的数据结果就拓展了一行。

  • 递归查询的结果集,在对新的锚定查询的结果集应用WHERE条件后,如果发现还可以返回数据,则继续生成新的临时CTE,并将该次锚定查询的结果集装入。比如上一步中得到了1901这个结果仍然<2024 ,那么就会重复创建临时CTE的过程,并将1901 当作锚定查询的结果集装入新的临时CTE,然后重复的计算->判断->创建临时CTE的过程。

上述的过程将会一直运行到新的锚定查询的结果集应用了WHERE条件后返回空数据集为止,因为返回空数据集就不需要再引用自身了。

之后临时CTE就可以将自身得到的结果集返回给上一层临时CTE进行拼接,逐层返回直到最开始的CTE中完整拼接,最终返回计算完成的CTE整体结束了数据的递归生成。

递归查询的一些应用场景

主流数据库中的递归查询示例

消息盒子

# 暂无消息 #

只显示最新10条未读和已读信息