暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL CTE递归语句示例

MySQL数据库学习笔记 2021-04-26
2131

前言:在许多场景中需要直接加载一颗树或者某个树的一部分,多层级数据情况下,如果单用数据库语句的情况下,需要用到CTE递归语句。


一、CTE语法(ORACLE、MySQL大同小异):

WITH cte_name (column_list) AS (

    query

) 

SELECT * FROM cte_name;


注意:

请注意,查询中的列数必须与column_list中的列数相同。如果省略column_listCTE使用定义CTE的查询的列列表。


二、CTE与递归的基本概念

2.1CTE(公用表表达式,Common Table Expression)

CTE是一个命名的临时结果集,仅在单个SQL语句(例如SELECT,INSERT,UPDATE或DELETE)的执行范围内存在。

与派生表(子查询)类似,CTE不作为对象存储,仅在查询执行期间持续。与派生表不同,CTE可以是自引用(递归CTE),也可以在同一查询中多次引用。此外,与派生表相比,CTE提供了更好的可读性和性能。

在Oracle有些许不同,这种功能被称为“子查询因子化”(Subquery Factoring)。


示例:


WITH customers_in_usa AS ( SELECT

customerName, state FROM

customers WHERE

country = 'USA'

) SELECT

customerName FROM

customers_in_usa -- 第1次引用WHERE

state = 'CA' UNION ALL SELECT

customerName FROM

customers_in_usa -- 第2次引用WHERE

state = 'LA'

2.2递归

程序调用自身的编程技巧称为递归( recursion)。上面的子查询中出现自身的别名就是一个例子。一般来说,递归需要有边界条件(终止条件)、递归前进段(执行逻辑)和递归返回段(终止执行并返回结果)。当边界条件不满足时,递归前进(继续执行);当边界条件满足时,递归返回(终止执行并返回结果)。


2.3递归CTE

综上所述,递归CTE是不断调用自己,直到满足终止条件才输出所有数据。终止条件一般是“某一次查询的结果集没有数据”(类似于exists子句的效果)。


三、实现示例:


3.1MySQL CTE实现树查找表结构:

CREATE TABLE `t_sys_organization` (

`org_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '组织机构Id',

`org_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '组织机构名称',

`parent_id` int(11) unsigned NOT NULL COMMENT '父节点为空的是根机构',

`level` int(11) DEFAULT NULL COMMENT '组织层级',

`is_valid` int(11) DEFAULT NULL COMMENT '是否有效', PRIMARY KEY (`org_id`),

UNIQUE KEY `idx_oname` (`org_name`) USING BTREE

) ENGINE=InnoDB AUTO_INCREMENT=84 DEFAULT CHARSET=utf8


--实现查找根节点下面所有子节点并带着部门名称

with recursive t_rec as -- recursive为关键字,可以不列出字段列表(

select

m.org_id

,m.org_name

,m.parent_id

,m.level

,concat('->',m.org_id) as nodepath -- 拼接各结点的id

,concat('->',m.org_name) as nodename from t_sys_organization m

where org_id = 1 union all

select

t1.org_id

,t1.org_name

,t1.parent_id

,t1.level

,concat(nodepath,'->',t1.org_id) -- 拼接各结点的id

,concat(nodename,'->',t1.org_name) -- 拼接各结点的id from t_sys_organization t1

join t_rec t2

on t1.parent_id = t2.org_id

-- 递归生成的结果集t2的parent_id与之前的源表id关联

)

select * from t_rec


3.2ORACLE实现 :cte方式


WITH t_rec( id,node_name,weight,parent_id

,root_id,node_path,node_level,total_weight

) AS -- 递归子查询必须列出字段列表(SELECT m.id

,m.node_name

,m.weight

,m.parent_id

,m.id AS root_id

,'->' || m.id AS node_path

,1 AS node_level

,m.weight AS total_weight FROM t_weight m

WHERE id = 1 UNION ALL SELECT t1.id

,t1.node_name

,t1.weight

,t1.parent_id

,t2.root_id -- 根结点

,t2.node_path || '->' || t1.id -- 拼接各结点的id

,t2.node_level + 1 -- 结点层级

,t2.total_weight + t1.weight -- 将根结点到当前结点的路径上各结点的值汇总FROM t_weight t1

JOIN t_rec t2

ON t1.parent_id = t2.id -- 递归生成的结果集t2的parent_id与之前的源表t1的id关联

)

SELECT * FROM t_rec


3.3ORACLE 实现方式:start with connect by


ORACLE 自带树查找语法:通过根节点找子节点:SELECT t.id

,t.node_name

,t.weight

,t.parent_id

,connect_by_root(t.id) AS root_id -- 根结点

,sys_connect_by_path(t.id, '->') AS node_path -- 拼接各结点的id

,LEVEL AS node_level -- 结点层级

,NULL AS total_weight -- 暂时无法实现累加操作FROM t_weight t

START WITH t.id = 10101

CONNECT BY prior t.parent_id = t.id

ORACLE 自带树查找语法:通过叶子节点找根节点:SELECT t.id

,t.node_name

,t.weight

,t.parent_id

,connect_by_root(t.id) AS root_id -- 根结点

,sys_connect_by_path(t.id, '->') AS node_path -- 拼接各结点的id

,LEVEL AS node_level -- 结点层级

,NULL AS total_weight -- 暂时无法实现累加操作FROM t_weight t

START WITH t.id = 1

CONNECT BY t.parent_id = PRIOR t.id


3.4其他示例:

从根节点开始查询:


Select t.*,level from table_name t start with t.id=0 connect by prior t.id=t.pid order by level


从子节点开始查询:


把上面的开始节点换成开始的子节点,并且prior 放在子节点前面


Select t.*,level from table_name t start with t.id=10 connect by t.id= prior t.pid order by level


eg2:

table:


CREATE TABLE `region` (

`id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,

`pid` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,

`deep` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,

`name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,

`pinyin_prefix` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,

`pinyin` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,

`ext_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,

`ext_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL, KEY `idx_region_pid` (`pid`) USING BTREE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci


--  查找所有叶子节点explain with recursive r as (

select id,name from region c where id=530403 union all

select c.id,CONCAT(r.name, '>', c.name) as name from region c,r where r.id = c.pid

)

select id,name from r;


-- 查找所有父节点


with recursive r as (

select id,pid,name from region c where id=110118115 union all

select c.id,c.pid,CONCAT(r.name, '>', c.name) as name from region c,r where c.id = r.pid

)

select pid,name from r;

select * from region




ALTER TABLE `demo`.`region`

ADD INDEX `idx_region_pid`(`pid`) USING BTREE; select * from region

文章转载自MySQL数据库学习笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论