公共表表达式(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整体结束了数据的递归生成。