MYSQL8.0 WITH RECURSIVE递归查询

Posted swadian2008

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MYSQL8.0 WITH RECURSIVE递归查询相关的知识,希望对你有一定的参考价值。

mysql 8.0 版本以上 使用 WITH RECURSIVE 实现递归

注意:写法比较简单,也比较灵活,但是只适用于MySQL8.0及以上版本,这种写法其实和  PostgreSQL 的写法是一样的。

WITH RECURSIVE 语法

WITH recursive 表名 AS ( 
	初始语句(非递归部分) 
	UNION ALL 
	递归部分语句
)
[ SELECT| INSERT | UPDATE | DELETE]

with recursive 由两部分组成。第一部分是非递归部分( union all 上方),第二部分是递归部分(union all下方)。递归部分第一次进入的时候使用非递归部分传递过来的参数,也就是第一行的数据值,进而得到第二行数据值,然后根据第二行数据值得到第三行数据值。

用法示例

定义下面这样的表,存储每个区域(省、市、区)的 id,名字及上级区域的 pid

create table tb(id varchar(3) , pid varchar(3) , name varchar(10)); 
 
insert into tb values('002' , 0 , '浙江省'); 
insert into tb values('001' , 0 , '广东省'); 
insert into tb values('003' , '002' , '衢州市');  
insert into tb values('004' , '002' , '杭州市') ; 
insert into tb values('005' , '002' , '湖州市');  
insert into tb values('006' , '002' , '嘉兴市') ; 
insert into tb values('007' , '002' , '宁波市');  
insert into tb values('008' , '002' , '绍兴市') ; 
insert into tb values('009' , '002' , '台州市');  
insert into tb values('010' , '002' , '温州市') ; 
insert into tb values('011' , '002' , '丽水市');  
insert into tb values('012' , '002' , '金华市') ; 
insert into tb values('013' , '002' , '舟山市');  
insert into tb values('014' , '004' , '上城区') ; 
insert into tb values('015' , '004' , '下城区');  
insert into tb values('016' , '004' , '拱墅区') ; 
insert into tb values('017' , '004' , '余杭区') ; 
insert into tb values('018' , '011' , '金东区') ; 
insert into tb values('019' , '001' , '广州市') ; 
insert into tb values('020' , '001' , '深圳市') ;

需要查出某个省,如浙江省,管辖的所有市及市辖地区,示例 sql 如下

with RECURSIVE cte as
(
    select a.id, a.name from tb a where id = '002'
    union all 
    select k.id, k.name from tb k inner join cte c on c.id = k.pid
) 
select id, name from cte;

执行结果如下

 实际开发中用到的例子

// 使用的是postgre,遍历设备树获取指定设备树和所有子节点的测点信息

WITH RECURSIVE tree AS (
	SELECT * FROM eqp_tree 
	WHERE del_flag = 0 AND node_code = #eqpCode
	UNION
	SELECT e.* FROM eqp_tree e INNER JOIN tree s ON e.parent_code = s.node_code AND e.del_flag = 0 
	) SELECT
	k2.id,
	k2.eqp_code,
	k2.mp_code,
	k2.mp_name,
	k2.measure_no,
	k2.measureParameters,
	k3.location_code,
	k3.location_name 
FROM
	tree k1
	JOIN (
	SELECT
		A.id,
		A.eqp_code,
		A.mp_code,
		A.mp_name,
		A.measure_no,
		A.measure_position,
		array_to_string ( array_agg ( B.measure_parameter ), '|' ) AS measureParameters 
	FROM
		measure_point_info A
		LEFT JOIN measure_point_samples_set B ON A.mp_code = B.mp_code 
		AND B.del_flag = 0 
	WHERE
		A.del_flag = 0 
	GROUP BY
		A.id,
		A.eqp_code,
		A.mp_code,
		A.mp_name,
		A.measure_no,
		A.measure_position 
	) k2 ON k2.eqp_code = k1.node_code
	LEFT JOIN configure_model_location K3 ON k3.location_code = k2.measure_position 
	AND k3.del_flag = 0

上边 SQL 中用到了 postgresql 数据库实现某一列的数据拼接:array_agg() ,摘录如下

postgre 中没有 group_concat 函数,网上大部分的解决办法,都是自定义一个 group_concat 函数。其实完全没必要,因为 postgre 中自带的 array_agg() 函数完全可以实现该功能。postgre  中的 array_agg(字段) 等价于mysql 中的 group_concat(字段)。如果想将拼接的数据转换成字符串,可以在 array_agg() 的基础上使用 array_to_string() 方法。即 array_to_string(array_agg(字段),','),其中 ',' 可以换成其他任意间隔符。

参考文章:mysql 递归函数with recursive的用法_cyan_orange的博客-CSDN博客_mysql recursive

mysql with recursive 递归用法

with recursive 是一个递归的查询子句,他会把查询出来的结果再次代入到查询子句中继续查询。

语法:

WITH RECURSIVE  cte_name  AS ( 
	初始语句(非递归部分) 
	UNION ALL 
	递归部分语句
)
[ SELECT| INSERT | UPDATE | DELETE]

例子1:

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte WHERE n < 5
)
SELECT * FROM cte;


该语句书写包括如下几步

  1. 设定递归语法,首先初始执行第一句SELECT 1,也可以写成select xxx from xxx where xxx
  2. 其结果给到n,当n值发生改变,就会执行:SELECT n + 1 FROM cte WHERE n < 5
  3. 最终结果给到n输出

注意:WITH AS () 后面必须跟着 [ SELECT| INSERT | UPDATE | DELETE] 语句,否则报错。

例子2:

有如下行政区划表

create table administrative_division2
(
    administrative_division_sn        bigint            not null comment '行政区划代码'
        primary key,
    parent_administrative_division_sn bigint            null comment '上级行政区划代码',
    status                            tinyint default 1 not null comment '状态',
    name                              varchar(28)       not null comment '名称*',
    full_name                         varchar(200)      null comment '行政区划全路径名称,省市县全路径名称'
)
    comment '行政区划';

INSERT INTO administrative_division2 (administrative_division_sn, parent_administrative_division_sn, status, name, full_name) VALUES (110000, null, 1, '北京市', '');
INSERT INTO administrative_division2 (administrative_division_sn, parent_administrative_division_sn, status, name, full_name) VALUES (110100, 110000, 1, '北京市市辖区', '');
INSERT INTO administrative_division2 (administrative_division_sn, parent_administrative_division_sn, status, name, full_name) VALUES (110101, 110100, 1, '东城区', '');
INSERT INTO administrative_division2 (administrative_division_sn, parent_administrative_division_sn, status, name, full_name) VALUES (110102, 110100, 1, '西城区', '');
INSERT INTO administrative_division2 (administrative_division_sn, parent_administrative_division_sn, status, name, full_name) VALUES (110105, 110100, 1, '朝阳区', '');


我们想补充full_name的值变成如下

那么可以用如下语句

update administrative_division2 ad,(
    with recursive res as (select administrative_division_sn sn, parent_administrative_division_sn pSn, name
                                       from administrative_division2 ad
                                       where parent_administrative_division_sn is null 
                                       union
                                       select ad2.administrative_division_sn as     sn,
                                              ad2.parent_administrative_division_sn pSn,
                                              concat(res.name, ad2.name)     as     name
                                       from res
                                                inner join administrative_division2 ad2
                                                           on res.sn = ad2.parent_administrative_division_sn)
    select * from res
    ) as t1 set ad.full_name=t1.name where ad.administrative_division_sn=t1.sn;

该语句执行解析如下

  1. 先设定开始的条件为父ID为空,即所有的父节点
select administrative_division_sn sn, parent_administrative_division_sn pSn, name from administrative_division2 ad where parent_administrative_division_sn is null
  1. 设置循环的条件为等于这个父节点的子节点
select ad2.administrative_division_sn as sn, ad2.parent_administrative_division_sn pSn, concat(res.name, ad2.name) as name from res inner join administrative_division2 ad2 on res.sn = ad2.parent_administrative_division_sn
  1. 执行完2此时的res.sn就等于子sn了,然后递归执行2
  2. 最终执行set ad.full_name=t1.name where ad.administrative_division_sn=t1.sn把名称赋值为2中的拼接了的名称concat(res.name, ad2.name)

以上是关于MYSQL8.0 WITH RECURSIVE递归查询的主要内容,如果未能解决你的问题,请参考以下文章

mysql with recursive 递归用法

mysql with recursive 递归用法

在 Teradata 中创建具有“with recursive”语句的递归视图

MySQL - WITH RECURSIVE AS 递归查询

MySQL - WITH RECURSIVE AS 递归查询

MySQL - WITH RECURSIVE AS 递归查询