当前位置: 代码网 > it编程>数据库>Mysql > MySQL 公用表达式的实现示例

MySQL 公用表达式的实现示例

2025年09月29日 Mysql 我要评论
公用表表达式和生成列是mysql 8.x版本中新增的特性。本篇文章将简单介绍mysql中新增的公用表表达式和生成列。公用表表达式从mysql 8.x版本开始支持公用表表达式(简称为cte)。公用表表达

公用表表达式和生成列是mysql 8.x版本中新增的特性。本篇文章将简单介绍mysql中新增的公用表表达式和生成列。

公用表表达式

从mysql 8.x版本开始支持公用表表达式(简称为cte)。公用表表达式通过with语句实现,可以分为非递归公用表表达式和递归公用表表达式。在常规的子查询中,派生表无法被引用两次,否则会引起mysql的性能问题。如果使用cte查询的话,子查询只会被引用一次,这也是使用cte的一个重要原因。

非递归cte

mysql 8.0之前,想要进行数据表的复杂查询,需要借助子查询语句实现,但sql语句的性能低下,而且子查询的派生表不能被多次引用。cte的出现极大地简化了复杂sql的编写,提高了数据查询的性能。

非递归cte的语法格式如下:

with
      cte_name [(col_name [, col_name] ...)] as (subquery)
      [, cte_name [(col_name [, col_name] ...)] as (subquery)] …
select [(col_name [, col_name] ...)] from cte_name;

可以对比子查询与cte的查询来加深对cte的理解。

子查询

例如:在mysql命令行中执行如下sql语句来实现子查询的效果。

mysql> select * from  (select year(now())) as year;
+-------------+
| year(now()) |
+-------------+
|        2025 |
+-------------+
1 row in set (0.01 sec)

上面的sql语句使用子查询获取当前年份的信息。

cte查询

使用cte实现查询的效果如下:

mysql> with year as (select year(now())) select * from year;
+-------------+
| year(now()) |
+-------------+
|        2025 |
+-------------+
1 row in set (0.01 sec)

通过两种查询的sql语句对比可以发现,使用cte查询能够使sql语义更加清晰。

cte定义多个字段

也可以在cte语句中定义多个查询字段,如下:

mysql> with cte_year_month (year, month) as
  (select year(now()) as year, month(now()) as month)
  select * from cte_year_month;
+------+-------+
| year | month |
+------+-------+
| 2025 |     8 |
+------+-------+
1 row in set (0.02 sec)

重用上次查询结果

cte可以重用上次的查询结果,多个cte之间还可以相互引用:

mysql> with cte1(cte1_year, cte1_month) as
  (select year(now()) as cte1_year, month(now()) as cte1_month),
  cte2(cte2_year, cte2_month) as
  (select (cte1_year+1) as cte2_year, (cte1_month + 1) as cte2_month from cte1) 
  select * from cte1 join cte2;
+-----------+------------+-----------+------------+
| cte1_year | cte1_month | cte2_year | cte2_month |
+-----------+------------+-----------+------------+
|      2025 |          8 |      2026 |          9 |
+-----------+------------+-----------+------------+
1 row in set (0.01 sec)

上面的sql语句中,在cte2的定义中引用了cte1。

注意:在sql语句中定义多个cte时,每个cte之间需要用逗号进行分隔。

递归cte

递归cte的子查询可以引用自身,相比非递归cte的语法格式多一个关键字recursive。

with recursive
      cte_name [(col_name [, col_name] ...)] as (subquery)
      [, cte_name [(col_name [, col_name] ...)] as (subquery)] …
select [(col_name [, col_name] ...)] from cte_name;

递归cte子查询类型

在递归cte中,子查询包含两种:

种子查询种子查询会初始化查询数据,并在查询中不会引用自身,

递归查询递归查询是在种子查询的基础上,根据一定的规则引用自身的查询。

这两个查询之间会通过union、union all或者union distinct语句连接起来。

例如:使用递归cte在mysql命令行中输出1~8的序列。

mysql> with recursive cte_num(num) as
  ( select 1 union all
  select num + 1 from cte_num where num < 8
  )
  select * from cte_num;
+-----+
| num |
+-----+
|   1 |
|   2 |
|   3 |
|   4 |
|   5 |
|   6 |
|   7 |
|   8 |
+-----+
8 rows in set (0.02 sec)

递归cte查询对于遍历有组织、有层级关系的数据时非常方便。

例如,创建一张区域数据表t_area,该数据表中包含省市区信息。

mysql> create table t_area(
  id int not null,
  name varchar(30),
  pid int
  );
query ok, 0 rows affected (0.02 sec)

向t_area数据表中插入测试数据。

mysql> insert into t_area (id, name, pid)
  values
  (1, '河北省', null),
  (2, '邯郸市', 1),
  (3, '邯山区', 2),
  (4, '复兴区', 2),
  (5, '河南省', null),
  (6, '郑州市', 5),
  (7, '中原区', 6);
query ok, 7 rows affected (0.01 sec)
records: 7  duplicates: 0  warnings: 0

sql语句执行成功,查询t_area数据表中的数据。

mysql> select * from t_area;
+----+--------+------+
| id | name   | pid  |
+----+--------+------+
|  1 | 河北省 | null |
|  2 | 邯郸市 |    1 |
|  3 | 邯山区 |    2 |
|  4 | 复兴区 |    2 |
|  5 | 河南省 | null |
|  6 | 郑州市 |    5 |
|  7 | 中原区 |    6 |
+----+--------+------+
7 rows in set (0.03 sec)

接下来,使用递归cte查询t_area数据表中的层级关系。

mysql> with recursive area_depth(id, name, path) as
  (
  select id, name, cast(id as char(300))
  from t_area where pid is null
  union all
  select a.id, a.name, concat(ad.path, '->', a.id)
  from area_depth as ad 
  join t_area as a
  on ad.id = a.pid
  )
  select * from area_depth order by path;
+----+--------+---------+
| id | name   | path    |
+----+--------+---------+
|  1 | 河北省 | 1       |
|  2 | 邯郸市 | 1->2    |
|  3 | 邯山区 | 1->2->3 |
|  4 | 复兴区 | 1->2->4 |
|  5 | 河南省 | 5       |
|  6 | 郑州市 | 5->6    |
|  7 | 中原区 | 5->6->7 |
+----+--------+---------+
7 rows in set (0.02 sec)

其中,path列表示查询出的每条数据的层级关系。

递归cte的限制

递归cte的查询语句中需要包含一个终止递归查询的条件。

当由于某种原因在递归cte的查询语句中未设置终止条件时,

mysql会根据相应的配置信息,自动终止查询并抛出相应的错误信息。

终止递归cte配置项

在mysql中默认提供了如下两个配置项来终止递归cte。

cte_max_recursion_depth:如果在定义递归cte时没有设置递归终止条件,当达到此参数设置的执行次数后,mysql报错。

max_execution_time:表示sql语句执行的最长毫秒时间,当sql语句的执行时间超过此参数设置的值时,mysql报错。

例如:未设置查询终止条件的递归cte, mysql会抛出错误信息并终止查询。

mysql>  with recursive cte_num (n) as
  (
  select 1
  union all
  select n+1 from cte_num
  )
  select * from cte_num;
recursive query aborted after 1001 iterations. try increasing @@cte_max_recursion_depth to a larger value.

从输出结果可以看出,当没有为递归cte设置终止条件时,mysql默认会在第1001次查询时抛出错误信息并终止查询。

查看cte_max_recursion_depth

查看cte_max_recursion_depth参数的默认值。

mysql> show variables like 'cte_max%';
+-------------------------+-------+
| variable_name           | value |
+-------------------------+-------+
| cte_max_recursion_depth | 1000  |
+-------------------------+-------+
1 row in set (0.02 sec)

结果显示,cte_max_recursion_depth参数的默认值为1000,所以mysql会在第1001次查询时抛出错误并终止查询。

设置cte_max_recursion_depth

接下来,验证mysql是如何根据max_execution_time配置项终止递归cte。

首先,为了演示max_execution_time参数的限制,

需要将cte_max_recursion_depth参数设置为一个很大的数字,

这里在mysql会话级别中设置。

mysql> set session cte_max_recursion_depth=999999999;
query ok, 0 rows affected (0.00 sec)

mysql> show variables like 'cte_max%';
+-------------------------+-----------+
| variable_name           | value     |
+-------------------------+-----------+
| cte_max_recursion_depth | 999999999 |
+-------------------------+-----------+
1 row in set (0.02 sec)

已经成功将cte_max_recursion_depth参数设置为999999999。

查看max_execution_time

查看mysql中max_execution_time参数的默认值。

mysql> show variables like 'max_execution%';
+--------------------+-------+
| variable_name      | value |
+--------------------+-------+
| max_execution_time | 0     |
+--------------------+-------+
1 row in set (0.00 sec)

在mysql中max_execution_time参数的值为毫秒值,默认为0,也就是没有限制。

设置max_execution_time

在mysql会话级别将max_execution_time的值设置为1s。

mysql> set session max_execution_time=1000; 
query ok, 0 rows affected (0.00 sec)

mysql> show variables like 'max_execution%';
+--------------------+-------+
| variable_name      | value |
+--------------------+-------+
| max_execution_time | 1000  |
+--------------------+-------+
1 row in set (0.02 sec)

已经成功将max_execution_time的值设置为1s。

当sql语句的执行时间超过max_execution_time设置的值时,mysql报错。

mysql> with recursive cte(n) as
  (
  select 1
  union all
  select n+1 from cte
  )
  select * from cte;
query execution was interrupted, maximum statement execution time exceeded

mysql提供的终止递归的机制(cte_max_recursion_depth和max_execution_time),有效地预防了无限递归的问题。

注意:虽然mysql默认提供了终止递归的机制,但是在使用mysql的递归cte时,建议还是根据实际的需求,在cte的sql语句中明确设置递归终止的条件。

另外,cte支持select/insert/update/delete等语句,这里只演示了select语句,其他语句可以自行实现。

生成列

mysql中生成列的值是根据数据表中定义列时指定的表达式计算得出的,主要包含两种类型:virsual生成列和sorted生成列,其中virsual生成列是从数据表中查询记录时,计算该列的值;sorted生成列是向数据表中写入记录时,计算该列的值并将计算的结果数据作为常规列存储在数据表中。

通常,使用的比较多的是virsual生成列,原因是virsual生成列不占用存储空间。

创建表时指定生成列

例如,创建数据表t_genearted_column,数据表中包含double类型的字段a、b和c,其中c字段是由a字段和b字段计算得出的,如下:

mysql>  create table t_genearted_column(
  a double,
  b double,
  c double as (a * a + b * b)
  );
query ok, 0 rows affected (0.07 sec)

向t_genearted_column数据表中插入数据。

mysql> insert into t_genearted_column
  (a, b)
  values
  (1, 1),
  (2, 2),
  (3, 3);
query ok, 3 rows affected (0.00 sec)
records: 3  duplicates: 0  warnings: 0

查询t_genearted_column数据表中的数据。

mysql> select * from t_genearted_column;
+---+---+----+
| a | b | c  |
+---+---+----+
| 1 | 1 |  2 |
| 2 | 2 |  8 |
| 3 | 3 | 18 |
+---+---+----+
3 rows in set (0.02 sec)

结果显示,在向t_genearted_column数据表中插入数据时,并没有向c字段中插入数据,

c字段的值是由a字段的值和b字段的值计算得出的。

如果在向t_genearted_column数据表插入数据时包含c字段,则向c字段插入数据时,必须使用default,否则mysql报错。

mysql> insert into t_genearted_column
  (a, b, c)
  values
  (4, 4, 32);
3105 - the value specified for generated column 'c' in table 't_genearted_column' is not allowed.

mysql报错,报错信息为不能为生成的列手动赋值。

使用default关键字代替具体的值。

mysql>  insert into t_genearted_column
  (a, b, c)
  values
  (4, 4, default);
query ok, 1 row affected (0.00 sec)

sql语句执行成功,查询t_genearted_column数据表中的数据。

mysql> select * from t_genearted_column;
+------+------+------+
| a    | b    | c    |
+------+------+------+
|    1 |    1 |    2 |
|    2 |    2 |    8 |
|    3 |    3 |   18 |
|    4 |    4 |   32 |
+------+------+------+
4 rows in set (0.00 sec)

已经成功为c字段赋值。

也可以在创建表时明确指定virsual生成列。

mysql> create table t_column_virsual (
  a double,
  b double,
  c double generated always as (a + b) virtual);
query ok, 0 rows affected (0.02 sec)

向t_column_virsual数据表中插入数据并查询结果。

mysql> insert into t_column_virsual
  (a, b)
  values
  (1, 1);
query ok, 1 row affected (0.00 sec)
mysql> select * from t_column_virsual;
+---+---+---+
| a | b | c |
+---+---+---+
| 1 | 1 | 2 |
+---+---+---+
1 row in set (0.02 sec)

为已有表添加生成列

可以使用alter table add column语句为已有的数据表添加生成列。

例如:创建数据表t_add_column。

mysql> create table t_add_column(
  a double,
  b double
  );
query ok, 0 rows affected (0.10 sec)

向数据表中插入数据。

mysql> insert into t_add_column (a, b) values (2, 2);
query ok, 1 row affected (0.01 sec)

为t_add_column数据表添加生成列。

mysql> alter table t_add_column add column c double generated always as(a * a + b * b) stored;
query ok, 1 row affected (0.15 sec)
records: 1  duplicates: 0  warnings: 0

sql语句执行成功,查询t_add_column数据表中的数据。

mysql> select * from t_add_column;
+---+---+---+
| a | b | c |
+---+---+---+
| 2 | 2 | 8 |
+---+---+---+
1 row in set (0.02 sec)

结果:当数据表中存在数据时,为数据表添加生成列,会自动根据已有的数据计算该列的值,并存储到该列中。

修改已有的生成列

例如:修改t_add_column数据表的生成列c,将其计算规则修改为a * b。

mysql> alter table t_add_column
  modify column c double
  generated always as (a * b)
  stored;
query ok, 1 row affected (0.05 sec)
records: 1  duplicates: 0  warnings: 0

查询t_add_column数据表中的数据。

mysql>  select * from t_add_column;
+---+---+---+
| a | b | c |
+---+---+---+
| 2 | 2 | 4 |
+---+---+---+
1 row in set (0.02 sec)

c列的值此时已经被修改为a列的值乘以b列的值的结果数据。

删除生成列

删除生成列可以使用alter table drop column语句实现。

例如:删除t_add_column数据表中的生成列c。

mysql> alter table t_add_column drop column c;
query ok, 1 row affected (0.10 sec)
records: 1  duplicates: 0  warnings: 0

sql语句执行成功,再次查看t_add_column数据表中的数据。

mysql> select * from t_add_column;
+---+---+
| a | b |
+---+---+
| 2 | 2 |
+---+---+
1 row in set (0.02 sec)

结果:生成列c已经被成功删除。

总结

到此这篇关于mysql 公用表达式的实现示例的文章就介绍到这了,更多相关mysql 公用表达式内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

  • MySQL之搜索引擎使用解读

    mysql的存储引擎是什么mysql当中数据用各种不同的技术存储在文件中,每一种技术都使用不同的存储机制,索引技巧 锁定水平,以及最终提供的不同的功能和能力,这些就是我们说的存储引…

    2025年09月27日 数据库
  • Docker MySQL启动问题及解决过程

    Docker MySQL启动问题及解决过程

    问题概述docker 环境中的 mysql 容器启动失败,主要表现为两个核心错误:临时文件权限问题:can't create/write to file '/t... [阅读全文]
  • MySQL误删数据或者丢失?这6种方案能帮你快速恢复!

    前言最近星球中有位小伙伴说:他不小心把测试环境mysql表中所有数据都误删了,问我要如何快速恢复?幸好他误删的是测试环境,非生产环境。我遇到过,之前有同事把生产环境会员表中的数据误…

    2025年09月27日 数据库
  • Nginx配置proxy protocol代理获取真实ip的全过程

    前言在现代开发中有很多场景需要拿到用户的真实ip,比如安全策略,和地区热点信息推送等功能,但是现在代理很多。用户可能会通过代理访问服务,或者黑客攻击的时候也会使用很多肉机隐藏其真实…

    2025年09月26日 数据库
  • CentOS中设置yum源的详细步骤

    CentOS中设置yum源的详细步骤

    在centos中设置yum源可以分为以下几个步骤。我将以设置阿里云镜像源为例进行说明:1. 备份原有yum源# 备份原有repo文件sudo cp -r /et... [阅读全文]
  • MySQL从视图到用户和权限管理操作

    一:视图的定义视图是⼀个虚拟的表,它是基于⼀个或多个基本表或其他视图的查询结果集。视图本⾝不存储数据,⽽是通过执⾏查询来动态⽣成数据。⽤⼾可以像操作普通表⼀样使⽤视图进⾏查询、更新…

    2025年09月26日 数据库

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com