MySQL5.7递归查询与CTE递归查询

news2024/12/22 18:57:15

文章目录

  • 一、8.0版本的递归
    • 1、CTE递归
    • 2、举例
    • 3、递归CTE的限制
  • 二、5.7版本的递归
    • 1、find_in_set 函数
    • 2、concat函数
    • 3、自定义函数实现递归查询
    • 4、向上递归
    • 5、可能遇到的问题

一、8.0版本的递归

1、CTE递归

先看8.0版本的递归查询CET。语法规则:

WITH RECURSIVE cte_name[(col_name [, col_name] ...)] AS (
    initial_query  -- anchor member
    UNION ALL
    recursive_query -- recursive member that references to the CTE name
)
SELECT * FROM cte_name;

以上SQL主要有三部分组成:

➢ 形成CTE结构的基本结果集的初始查询(initial_query),初始查询部分被称为锚成员

➢ 递归查询部分是引用CTE名称的查询,因此称为递归成员。递归成员由一个UNION、UNION ALL或者UNION DISTINCT运算符与锚成员相连

➢ 终止条件是当递归成员没有返回任何行时,确保递归停止

2、举例

递归某公司部门信息,用下其他帖子的测试数据:

DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept`  (
  `id` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `pid` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1000', '总公司', NULL);
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1001', '北京分公司', '1000');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1002', '上海分公司', '1000');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1003', '北京研发部', '1001');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1004', '北京财务部', '1001');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1005', '北京市场部', '1001');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1006', '北京研发一部', '1003');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1007', '北京研发二部', '1003');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1008', '北京研发一部一小组', '1006');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1009', '北京研发一部二小组', '1006');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1010', '北京研发二部一小组', '1007');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1011', '北京研发二部二小组', '1007');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1012', '北京市场一部', '1005');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1013', '上海研发部', '1002');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1014', '上海研发一部', '1013');
INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1015', '上海研发二部', '1013');

此时表数据:

在这里插入图片描述

接下来写这个树形结构的CET递归SQL:

with recursive t_tem as (
select * from  dept  where  id= '1001'
union all
 select d.* from dept d inner join t_tem t on t.id = d.pid
)
select *  from t_tem ;

  • t_tem 是一个表名
  • 使用UNION ALL 不断将每次递归得到的数据加入到表t_tem 中
  • select * from dept where id= ‘1001’ 即t_tem 表中的初始数据是id=1000的记录,即根节点
  • 通过inner join t_tem t on t.id = d.pid 找到id='1001’的下级节点
  • 最后select * from t_tem 拿递归得到的所有数据

在这里插入图片描述

以上是向下递归,即查父查子。向上递归则稍微改一下就行:

with recursive t_tem as (
select * from  dept  where  id= '1011'
union all
 select d.* from dept d inner join t_tem t on t.pid = d.id
)
select *  from t_tem ;

在这里插入图片描述

3、递归CTE的限制

递归CTE的查询语句中需要包含一个终止递归查询的条件。当由于某种原因在递归CTE的查询语句中未设置终止条件时,MySQL会根据相应的配置信息,自动终止查询并抛出相应的错误信息。在MySQL中默认提供了如下两个配置项来终止递归CTE。

  • cte_max_recursion_depth:如果在定义递归CTE时没有设置递归终止条件,当达到cte_max_recursion_depth参数设置的执行次数后,MySQL会报错。
  • max_execution_time:表示SQL语句执行的最长毫秒时间,当SQL语句的执行时间超过此参数设置的值时,MySQL报错。
---查看、修改cte_max_recursion_depth参数的默认值
--- 默认1000
SHOW VARIABLES LIKE 'cte_max%';
--- 会话级别设置该值
SET SESSION cte_max_recursion_depth=999999999;

---查看、修改MySQL中max_execution_time参数的默认值
--- 0:表示没有限制
SHOW VARIABLES LIKE 'max_execution%';
---单位为毫秒
SET SESSION max_execution_time=1000; 

二、5.7版本的递归

8.0以下不支持CTE递归,先看下要用的几个函数。

1、find_in_set 函数

语法:
find_in_set(str,strlist)
作用:
查找 str 字符串在字符串 strlist 中的位置,返回结果为 1 ~ n 。若没有找到,则返回0
举例:
select FIND_IN_SET('c','c,o,d,e'); 

在这里插入图片描述

举例:
select * from dept where FIND_IN_SET(id,'1000,1001,1002'); 

在这里插入图片描述

到此,如果我们可以把某个节点的和其所有子节点的id用逗号拼接成上面函数中的strlist,就可以得到所需的数据了。

2、concat函数

concat函数是MySQL中用来拼接字符串的。

语法:
select CONCAT(column 1, column 2) from xxx

在这里插入图片描述

在concat的基础上,还有concat_ws 则可以指定分隔符,第一个参数传入分隔符。

select CONCAT_WS("...","Ha","ha","ha");

在这里插入图片描述

group_concat函数 可以对将要拼接的字段值去重,也可以排序,指定分隔符。若没有指定,默认以逗号分隔

group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator '分隔符'] )

3、自定义函数实现递归查询

delimiter $$ 
drop function if exists get_child_list$$ 
create function get_child_list(in_id varchar(10)) returns varchar(1000) 
begin 
    declare ids varchar(1000) default ''; 
    declare tempids varchar(1000); 
 
    set tempids = in_id; 
    while tempids is not null do 
        set ids = CONCAT_WS(',',ids,tempids); 
        select GROUP_CONCAT(id) into tempids from dept where FIND_IN_SET(pid,tempids)>0;  
    end while; 
    return ids; 
end  
$$ 
delimiter ; 
解释:
  • delimiter $$ ,定义结束符为$$,MySQL默认语句的结束为封号,但在函数定义中我希望封号不是结束。
  • drop function if exists get_child_list,和drop table if exists xx目的类似
  • create function get_child_list 创建函数,后面是传形参的类型和形参名、返回值类型
  • begin 和 end 中间包围的就是函数体,真正的逻辑部分
  • declare 声明变量,default 给变量设置默认值,这里声明ids是为了后面拼接递归字符串,并返回给调用者
  • tempids 是为了记录下边 while 循环中临时生成的所有子节点以逗号拼接成的字符串
  • set 用来给变量赋值
  • while do … end while; 循环语句,end while 末尾需要加上分号
  • CONCAT_WS 函数把最终结果 ids 和 临时生成的 tempids 用逗号拼接起来
  • FIND_IN_SET(pid,tempids)>0 为条件,遍历在 tempids 中的所有 pid
  • GROUP_CONCAT(id) into tempids 把这些子节点 id 都用逗号拼接起来,并覆盖更新 tempids
  • 等下次循环进来时,就会再次拼接并覆盖tempids ,并再次查找所有子节点的所有子节点。没有子节点时,拼接为空,tempids为空,循环结束
  • delimiter ; 把结束符重新设置为默认的结束符分号

定义变量:
declare 变量名[,变量名2…] 变量类型 [default 默认值]

赋值变量:
set 变量名1=变量值1(或者表达式)[ ,变量名2=变量值2(或者表达式)]

使用变量:
select 列名[,列名…] into 变量名1[,变量名二…]

以这个表为例:

在这里插入图片描述

调用上面定义的函数,传参1000:ids='' , tempids=1000,进入while循环

- 第一次循环:ids = 1000, tempids = 1001,1002(找pid为tempids的节点id拼接,并覆盖tempids的值,到此,1000子节点的id被全部拿走拼接)
- 第二次循环:ids=100010011002  tempids=1003110410051013(到此,id为1000的孙子节点全部拿到)
- 第三次循环:ids=1000100110021003110410051013,tempids = 1006,1007,1012,1014,1015(id为1000的孙节点的子节点全部拿到)
- 第四次循环:ids=10001001100210031104100510131006,1007,1012,1014,1015  tempids = 1008,1009,1010,1011
- 第五次循环:ids=10001001100210031104100510131006,1007,1012,1014,10151008,1009,1010,1011 tempids = null(上一次循环的id,不是任何节点的pid,即没有子节点了,遍历完了)
- while tempids is not null不成立,跳出循环

可以看到调用函数输出和分析的一样,能得到某个节点的所有子节点的id

在这里插入图片描述

//查询某个节点的各级子节点
select * from dept where find_in_set(id,get_child_list(1001));

在这里插入图片描述

4、向上递归

delimiter $$ 
drop function if exists get_parent_list$$ 
create function get_parent_list(in_id varchar(10)) returns varchar(1000) 
begin 
    declare ids varchar(1000) default ''; 
    declare tempids varchar(1000); 
 
    set tempids = in_id; 
    while tempids is not null do 
        set ids = CONCAT_WS(',',ids,tempids); 
        select pid into tempids from dept where id = tempids;  
    end while; 
    return ids; 
end  
$$ 
delimiter ; 

有点不同的是,这里一个节点的父节点唯一,不用拼接,直接select pid into tempids即可

在这里插入图片描述

5、可能遇到的问题

问题一:创建函数报错:

ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you might want to use the less safe log_bin_trust_function_creators variable)

原因:

因为开启了bin-log, 就必须指定我们的函数是否是:

1 DETERMINISTIC 不确定的

2 NO SQL 没有SQl语句,当然也不会修改数据

3 READS SQL DATA 只是读取数据,当然也不会修改数据

4 MODIFIES SQL DATA 要修改数据

5 CONTAINS SQL 包含了SQL语句

其中在function里面,只有 DETERMINISTIC, NO SQLREADS SQL DATA 被支持。如果开启了 bin-log, 就必须为定义的function指定一个参数(log_bin_trust_function_creators )。

在这里插入图片描述

1)临时使用(重启后失效)
SET GLOBAL log_bin_trust_function_creators = 1;

2)永久生效
在my.cnf里面设置
log-bin-trust-function-creators=1
然后重启MySQL服务

问题二:遍历的结果不全

group_concat 函数来拼接字符串是有长度限制的,默认为 1024 字节。
//查看拼接的长度限制
show variables like "group_concat_max_len";
//单位是字节,不是字符。在 MySQL 中,单个字母占1个字节,而我们平时用的 utf-8下,一个汉字占3个字节

解决:

方式一:修改配置文件 my.cnf ,增加:

group_concat_max_len = 102400000 #需要的最大长度

方式二:临时生效

SET GLOBAL group_concat_max_len=102400; 

或者

SET SESSION group_concat_max_len=102400;

区别在于,global是全局的,任意打开一个新的会话都会生效,但是注意,已经打开的当前会话并不会生效。而 session 是只会在当前会话生效,其他会话不生效。但都是重启后失效。



参考文章:

https://segmentfault.com/a/1190000023471353

https://www.cnblogs.com/wsx2019/p/15709044.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/580017.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入浅出解析Stable Diffusion完整核心基础知识 | 【算法兵器谱】

Rocky Ding 公众号:WeThinkIn 写在前面 【算法兵器谱】栏目专注分享AI行业中的前沿/经典/必备的模型&论文,并对具备划时代意义的模型&论文进行全方位系统的解析,比如Rocky之前出品的爆款文章Make YOLO Great Again系列。也欢迎大家提…

笔试强训错题总结(一)

笔试强训错题总结 文章目录 笔试强训错题总结选择题编程题连续最大和不要二最近公共祖先最大连续的bit数幸运的袋子手套 选择题 以下程序的运行结果是&#xff08;&#xff09; #include <stdio.h> int main(void) {printf("%s , %5.3s\n", "computer&q…

<Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的beep驱动

&#xff1c;Linux开发&#xff1e;驱动开发 -之-基于pinctrl/gpio子系统的beep驱动 交叉编译环境搭建&#xff1a; &#xff1c;Linux开发&#xff1e; linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下&#xff1a; &#xff1c;Linux开发&#xff1e; -之-系统移植…

如何在华为OD机试中获得满分?Java实现【人民币转换】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

认识Servlet---1

hi ,大家好,今天为大家带来Servlet相关的知识,并且实现第一个程序 &#x1f389;1.什么是Servlet &#x1f389;2.使用Servlet写一个hello程序 &#x1f33b;&#x1f33b;&#x1f33b;1.创建项目 &#x1f33b;&#x1f33b;&#x1f33b;2.引入依赖 &#x1f33b;&…

GitHub基本概念

创建日期: 2018-09-22 09:50:06 Git & GitHub Git是一个版本控制软件&#xff1a; 读作[gɪt] ,拼音读作gē y te。 Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed an…

STM32之温湿度LCD显示并上传服务器

目录 项目需求 项目框图 硬件清单 LCD1602介绍及实战 硬件接线 引脚封装 代码实现 DHT11介绍及实战 硬件接线 引脚封装 代码实现 项目设计及实现 项目设计 项目实现 项目需求 使用温湿度传感器模块&#xff08; DHT11 &#xff09;获取温度及湿度&#xff0c…

推荐计算机领域的几本入门书籍

人工智能入门&#xff1a; 人工智能&#xff1a;现代方法&#xff08;第4版&#xff09;揭示AI与chatgpt的奥秘&#xff0c;详解人工智能的发展与未来&#xff01; 推荐理由&#xff1a;系统性总结人工智能的方方面面&#xff0c;国际人工智能领域专家斯图尔特罗素撰写人工智能…

YOLO-NAS对象检测算法再一次颠覆YOLO系列算法——已超越YOLOv8

对象检测彻底改变了机器感知和解释人类世界的方式。这是计算机视觉中一项特别关键的任务,使机器能够识别和定位图像或视频中的物体。如自动驾驶汽车、面部识别系统等。推动对象检测进步的一个关键因素是发明了神经网络架构。强大的神经网络推动了对象检测的进步,增强了计算机…

Meta Learning

Meta Learning&#xff08;元学习&#xff09;是一种机器学习技术&#xff0c;它的核心思想是学习如何学习。 Meta Learning的目标是从以前的学习经验中学习到通用的学习策略和模式&#xff0c;以便在新的任务上快速适应和学习。 Meta Learning的核心思想是将学习任务视为元任…

Vivado下阻塞赋值和非阻塞赋值的对比

Verilog 基础知识 中已经介绍过了阻塞赋值和非阻塞赋值的区别&#xff0c;下面通过一个在Vivado中的简单例子来直观的反映两者的不同。 首先给出设计源代码如下。 module block(a,b,c,clk,x);input x;input clk;output reg a,b,c;always(posedge clk) begina x; //阻塞赋值…

零钱兑换,凑零钱问题,从暴力递归到动态规划(java)

凑零钱问题&#xff0c;从暴力递归到动态规划 leetcode 322 题 零钱兑换暴力递归&#xff08;这个会超时&#xff0c;leetcode 跑不过去&#xff09;递归缓存 leetcode 322 题 零钱兑换 322 零钱兑换 - 可以打开链接测试 给你一个整数数组 coins &#xff0c;表示不同面额的硬…

[MAUI]模仿Chrome下拉标签页的交互实现

文章目录 创建粘滞效果的圆控件贝塞尔曲线绘制圆创建控件创建形变可控形变形变边界形变动画 创建手势控件创建页面布局更新拖拽物位置其它细节 项目地址 今天来说说怎样在 .NET MAUI 中制作一个灵动的类标签页控件&#xff0c;这类控件常用于页面中多个子页面的导航功能。 比如…

《数据库应用系统实践》------ 公园游客日流量管理系统

系列文章 《数据库应用系统实践》------ 公园游客日流量管理系统 文章目录 系列文章一、需求分析1、系统背景2、 系统功能结构&#xff08;需包含功能结构框图和模块说明&#xff09;3&#xff0e;系统功能简介 二、概念模型设计1&#xff0e;基本要素&#xff08;符号介绍说明…

【阅读笔记】概率预测之MQ-RNN(含Pytorch代码实现)

本文作为自己阅读论文后的总结和思考&#xff0c;不涉及论文翻译和模型解读&#xff0c;适合大家阅读完论文后交流想法&#xff0c;关于论文翻译可以查看参考文献。论文地址&#xff1a;https://arxiv.org/abs/1711.11053 MQ-RNN 一. 全文总结二. 研究方法三. 结论四. 创新点五…

谷歌推出免费AI编程神器Colab,欲将Copilot拉下神坛

在如今的AI编码工具领域&#xff0c;微软旗下的Github Copilot可以算得上是一家独大&#xff0c;而作为老对手的谷歌显然并不愿屈服于此。 近日&#xff0c;谷歌通过其官网高调发文宣布&#xff0c;将为研发工具Google Colaboratory&#xff08;Colab&#xff09;加入全新的AI…

DAY 68 redis高可用的主从复制、哨兵、cluster集群

Redis 高可用 什么是高可用 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。 但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#xff0c;除了保证提供正常服…

RT-Thread memheap 开启多块 SRAM的方法

验证环境 NUCLEO-L476RG 开发板&#xff0c;板载 STM32L476RGT6&#xff08;96K SARM1 32K SRAM2&#xff09; Win10 64 位 Keil MDK 5.36 RT-Thread 5.0.1 版本&#xff08;2023-05-28 master 主线&#xff09; 功能描述 最近在研究 RT-Thread 内存的管理&#xff0c;熟…

Linux内核源码分析 2:Linux内核版本号和源码目录结构

一、Linux的版本 1. 稳定版和开发版 Linux内核主要分为两种版本&#xff1a; 稳定版&#xff08;长期支持版&#xff09;&#xff1a;稳定版的内核具有工业级的强度&#xff0c;可以广泛地应用和部署。而每一代新推出的稳定版内核大部分都只是修正了一些Bug或是加入了一些新的…

【网络协议详解】——FTP系统协议(学习笔记)

目录 &#x1f552; 1. 概述&#x1f552; 2. 工作原理&#x1f558; 2.1 两个连接 &#x1f552; 3. 相关命令与处理&#x1f558; 3.1 接入命令&#x1f558; 3.2 文件管理命令&#x1f558; 3.3 数据格式化命令&#x1f558; 3.4 端口定义命令&#x1f558; 3.5 文件传输命令…