引言
递归查询通常用于树形结构数据的查询。很多数据库(PG、MYSQL、SQLSERVER)都支持使用通用表表达式(CTE)
和 (WITH RECURSIVE)
关键字实现递归查询。
CTE(Common Table Expressions,通常使用With开头,允许在查询中创建可被引用的临时表。
递归查询的一般形式
递归查询是通过在CTE
中添加RECURSIVE
关键字实现的,其一般形式如下:
sql
WITH RECURSIVE cte_name AS (
non_recursive_term
UNION [ALL]
recursive_term
)
invocation_statement
其中,
cte_name
是用于实现递归查询的一个公共表表达式的名称;non_recursive_term
非递归项recursive_term
递归项nvocation_statement
则是公共表表达式的调用语句
Postgresql递归查询的执行过程
本部分内容摘自PGSQL官网文档
- 执行非递归项,将返回的结果记为 <math xmlns="http://www.w3.org/1998/Math/MathML"> R i R_i </math>Ri(此时 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i=0)。
- 重复以下步骤,直至满足递归终止条件:
- 检查 <math xmlns="http://www.w3.org/1998/Math/MathML"> R i R_i </math>Ri是否为空,如果为空则终止递归,直接跳到步骤3。
- 将 <math xmlns="http://www.w3.org/1998/Math/MathML"> R i R_i </math>Ri替换递归项中的递归自引用,然后执行递归项,(如果使用了
UNION
而非UNION ALL
,则会自动去除与之前结果行重复的行,并将返回的剩余记录记为 <math xmlns="http://www.w3.org/1998/Math/MathML"> R i + 1 R_{i+1 } </math>Ri+1。 - 记 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i= <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i+1。
【实践】记录一次工作中碰到的树结构递归查询死循环问题
一个类似这样的数据表:
id | pid |
---|---|
A | B |
B | A |
使用这样的递归查询语句:
sql
WITH RECURSIVE TEST (
SELECT id,pid from tree
union all -- union all会返回所有的数据,不进行去重操作
select id,pid from tree left join TEST on tree.id = TEST.pid
)
selct * from TEST
理论上这样的SQL会无限循环,实际执行时也是这样。但只要把union all
改为 union
,sql查询很快就会返回结果了。
推测:union 去重操作也会影响终止条件,推测是数据库引擎内部会把UNION后的结果集 <math xmlns="http://www.w3.org/1998/Math/MathML"> R i + 1 R_{i+1 } </math>Ri+1 与上一次递归循环结果集 <math xmlns="http://www.w3.org/1998/Math/MathML"> R i R_i </math>Ri进行比较。
PS:后边查询官网的执行过程也印证了这个说法。