MySQL窗口函数

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: MySQL8.0的窗口函数使我们部分功能需求查询的实现更加快捷简单,本文主要包括两部分:一部分是对MySQL8.0窗口函数的介绍,另外一部分是MySQL8.0之前我如何利用SQL是想部分窗口函数的功能示例。

一、Mysql8.0 窗口函数

1、窗口函数与聚合函数的区别

MySQL从8.0开始支持窗口函数,窗口函数也叫分析函数。窗口函数更多的是业务中需要对数据做排序/分组排序,环比计算,百分比计算等需求。与聚合函数明显的不同是,聚合函数通过会将所有记录进行分类聚合;而窗口函数是对所有数据记录按照指定窗口进行计算,不会进行聚合。

在MySQL8.0之前我们其实也可以通过一定的办法实现窗口函数的功能,具体如何实现请见下文。

2、窗口函数基本语法

窗口函数主要由窗口函数、over子句、window子句、partition子句、order子句、frame子句。

示例:

SELECT time,subject,val,
    FIRST_VALUE(val) OVER w AS 'first',         //窗口函数以及over子句
    LAST_VALUE(val) OVER w AS 'last', 
    NTH_VALUE(val, 2) OVER w AS 'second', 
    NTH_VALUE(val, 4) OVER w AS 'fourth'
FROM observations 
WINDOW w AS (                                   //window子句  
PARTITION BY subject                            //partition子句
ORDER BY time                                   //order子句
ROWS UNBOUNDED PRECEDING                        //frame子句
);
+----------+---------+------+-------+------+--------+--------+
| time     | subject | val  | first | last | second | fourth |
+----------+---------+------+-------+------+--------+--------+
| 07:00:00 | st113   |   10 |    10 |   10 |   NULL |   NULL |
| 07:15:00 | st113   |    9 |    10 |    9 |      9 |   NULL |
| 07:30:00 | st113   |   25 |    10 |   25 |      9 |   NULL |
| 07:45:00 | st113   |   20 |    10 |   20 |      9 |     20 |
| 07:00:00 | xh458   |    0 |     0 |    0 |   NULL |   NULL |
| 07:15:00 | xh458   |   10 |     0 |   10 |     10 |   NULL |
| 07:30:00 | xh458   |    5 |     0 |    5 |     10 |   NULL |
| 07:45:00 | xh458   |   30 |     0 |   30 |     10 |     30 |
| 08:00:00 | xh458   |   25 |     0 |   25 |     10 |     30 |
+----------+---------+------+-------+------+--------+--------+
  • 窗口函数

按照具体业务需求指定具体的窗口函数,常见窗口函数如下:

函数 函数意义
row_number() 排序函数,并列记录也按照+1进行正常排序
rank() row 2 排序函数,并列记录同序号,并列记录后的记录按照总记录+1进行正常排序
dense_rank() 排序函数,并列记录同序号,并列记录后的记录按照并列记录+1进行正常排序
percent_rank() 分布函数,计算当前记录排序的(rank() - 1) 除以 (分组总记录数 - 1)的百分比
cume_dist() 分布函数,计算当前记录排序rank()的总记录数除以分组总记录数的百分比
lag(col,N) 按照分组排序,显示该记录的前N个的col值
lead(col,N) 按照分组排序,显示该记录的后N个的col值
first_value(col) 按照分组排序,显示排序第一的col值
last_value(col) 按照分组排序,显示截止到该记录排序最后的col值
nth_value(col,N) 按照分组排序下,截止到当前记录第N排序的col值
nfile(N) 按照分组排序,将所有记录分为N份
  • over子句

关键字,over() w as col_name中,w为别名,指定了具体的分组排序规则,确定窗口函数的计算范围,也可以将partion子句、order子句写在over()中直接使用。

over(partition by aa order by bb) w as col_name
<=>
over() w as col_name
window w as (partition by aa order by bb)
  • window子句

指定了具体的分组排序规则,确定窗口函数的计算范围,若SQL中需要多个窗口时可使用不同的别名来区分

  • partition子句
window w as (partition by aa)     //按照aa进行分组,窗口函数每个分组单独计算

指定分组规则,若不需要可不写

  • order子句
    指定排序规则,可以单独使用,也可以配合partition使用
window w as (partition by aa order by bb) //按照aa进行分组,每组记录按照bb进行顺序排序
window w as (order by bb desc)            //符合where条件的全部记录按照bb倒叙排序
  • frame子句

指定窗口函数的计算范围

语法:ROWS + 边界关键字

CURRENT ROW 以当前记录为左/右侧边界
UNBOUNDED PRECEDING 以分组中的第一条记录为左侧边界
UNBOUNDED FOLLOWING 以分组中的最后一条记录为右侧边界
N PRECEDING  以当前记录的前N条记录为左侧边界
N FOLLOWING  以当前记录的后N条记录为右侧边界

示例:

window w as (rows between N preceding and M following)    // 窗口范围是当前记录的前N条记录到后M条记录
window w as (rows unbounded preceding)      //窗口范围是当前记录到分组中的最后一条记录。
window w as (rows between unbounded preceding and unbounded following) //默认该模式,窗口范围是当前分组中所有记录范围进行计算

二、MySQL8.0之前实现窗口函数的办法

在MySQL8.0之前时没有窗口函数的,MySQL也可以实现部分窗口函数的功能,以下是部分示例:

1、 row_number()实现

MySQL8.0

无分组排序:
root@mysql 22:27:  [test]> select row_number() over w as row_num,class_num,user_id,score from score where subject_name='Math' window  w as ( order by score desc) ;
+---------+-----------+---------+--------+
| row_num | class_num | user_id | score  |
+---------+-----------+---------+--------+
|       1 | 001       |       1 | 100.00 |
|       2 | 002       |       5 |  98.00 |
|       3 | 002       |       8 |  98.00 |
|       4 | 001       |       3 |  92.00 |
|       5 | 001       |       2 |  89.00 |
|       6 | 002       |       9 |  89.00 |
|       7 | 001       |      10 |  89.00 |
|       8 | 002       |       4 |  87.00 |
|       9 | 002       |       6 |  77.00 |
|      10 | 001       |       7 |  73.00 |
+---------+-----------+---------+--------+
10 rows in set (0.00 sec)

分组排序:
root@mysql 22:50:  [test]> select row_number() over w as row_num,class_num,user_id,score from score where subject_name='Math' window  w as ( partition by class_num order by score desc) ;
+---------+-----------+---------+--------+
| row_num | class_num | user_id | score  |
+---------+-----------+---------+--------+
|       1 | 001       |       1 | 100.00 |
|       2 | 001       |       3 |  92.00 |
|       3 | 001       |       2 |  89.00 |
|       4 | 001       |      10 |  89.00 |
|       5 | 001       |       7 |  73.00 |
|       1 | 002       |       5 |  98.00 |
|       2 | 002       |       8 |  98.00 |
|       3 | 002       |       9 |  89.00 |
|       4 | 002       |       4 |  87.00 |
|       5 | 002       |       6 |  77.00 |
+---------+-----------+---------+--------+
10 rows in set (0.00 sec)

MySQL之前

无分组排序:
root@mysql 22:27:  [test]> select @r:=@r+1 as `row_number`,class_num,user_id,score from score,(select @r:=0) temp order by score desc;
+------------+-----------+---------+--------+
| row_number | class_num | user_id | score  |
+------------+-----------+---------+--------+
|          1 | 001       |       1 | 100.00 |
|          2 | 002       |       5 |  98.00 |
|          3 | 002       |       8 |  98.00 |
|          4 | 001       |       3 |  92.00 |
|          5 | 001       |       2 |  89.00 |
|          6 | 002       |       9 |  89.00 |
|          7 | 001       |      10 |  89.00 |
|          8 | 002       |       4 |  87.00 |
|          9 | 002       |       6 |  77.00 |
|         10 | 001       |       7 |  73.00 |
+------------+-----------+---------+--------+
10 rows in set, 2 warnings (0.00 sec)

分组排序:
root@mysql 22:48:  [test]> select `row_number`,class_num,user_id,score from (select if(@class=class_num,@r:=@r+1,@r:=1) as `row_number`,@class:=class_num,class_num,user_id,score from score,(select @r:=0,@class=NULL) temp order by class_num,score desc) tmp2;
+------------+-----------+---------+--------+
| row_number | class_num | user_id | score  |
+------------+-----------+---------+--------+
|          1 | 001       |       1 | 100.00 |
|          2 | 001       |       3 |  92.00 |
|          3 | 001       |       2 |  89.00 |
|          4 | 001       |      10 |  89.00 |
|          5 | 001       |       7 |  73.00 |
|          1 | 002       |       5 |  98.00 |
|          2 | 002       |       8 |  98.00 |
|          3 | 002       |       9 |  89.00 |
|          4 | 002       |       4 |  87.00 |
|          5 | 002       |       6 |  77.00 |
+------------+-----------+---------+--------+
10 rows in set, 4 warnings (0.00 sec)

2、dense_rank()实现

MySQL8.0

无分组排序:
root@mysql 22:27:  [test]> select dense_rank() over w as `dense_rank`,class_num,user_id,score from score where subject_name='Math' window  w as ( order by score desc) ;
+------------+-----------+---------+--------+
| dense_rank | class_num | user_id | score  |
+------------+-----------+---------+--------+
|          1 | 001       |       1 | 100.00 |
|          2 | 002       |       5 |  98.00 |
|          2 | 002       |       8 |  98.00 |
|          3 | 001       |       3 |  92.00 |
|          4 | 001       |       2 |  89.00 |
|          4 | 002       |       9 |  89.00 |
|          4 | 001       |      10 |  89.00 |
|          5 | 002       |       4 |  87.00 |
|          6 | 002       |       6 |  77.00 |
|          7 | 001       |       7 |  73.00 |
+------------+-----------+---------+--------+
10 rows in set (0.00 sec)

分组排序:
root@mysql 23:11:  [test]> select dense_rank() over w as `dense_rank`,class_num,user_id,score from score where subject_name='Math' window  w as ( partition by class_num order by score desc) ;
+------------+-----------+---------+--------+
| dense_rank | class_num | user_id | score  |
+------------+-----------+---------+--------+
|          1 | 001       |       1 | 100.00 |
|          2 | 001       |       3 |  92.00 |
|          3 | 001       |       2 |  89.00 |
|          3 | 001       |      10 |  89.00 |
|          4 | 001       |       7 |  73.00 |
|          1 | 002       |       5 |  98.00 |
|          1 | 002       |       8 |  98.00 |
|          2 | 002       |       9 |  89.00 |
|          3 | 002       |       4 |  87.00 |
|          4 | 002       |       6 |  77.00 |
+------------+-----------+---------+--------+
10 rows in set (0.00 sec)

Mysql8.0之前

无分组排序:
root@mysql 23:09:  [test]> select case when  @s = score then @r WHEN @s := score then @r := @r + 1 end as `dense_rank`,class_num,user_id,score from score,(select @r := 0, @s = NULL) temp where subject_name = 'Math' order by score desc ;
+------------+-----------+---------+--------+
| dense_rank | class_num | user_id | score  |
+------------+-----------+---------+--------+
|          1 | 001       |       1 | 100.00 |
|          2 | 002       |       5 |  98.00 |
|          2 | 002       |       8 |  98.00 |
|          3 | 001       |       3 |  92.00 |
|          4 | 001       |       2 |  89.00 |
|          4 | 002       |       9 |  89.00 |
|          4 | 001       |      10 |  89.00 |
|          5 | 002       |       4 |  87.00 |
|          6 | 002       |       6 |  77.00 |
|          7 | 001       |       7 |  73.00 |
+------------+-----------+---------+--------+
10 rows in set, 3 warnings (0.00 sec)

分组排序:
root@mysql 23:10:  [test]> select `dense_rank`,class_num,user_id,score from (select if(@class=class_num,case when  @s = score then @r when @s := score then @r := @r + 1 end,@r:=1) as `dense_rank`,@class:=class_num,@s:=score,class_num,user_id,score FROM score, ( SELECT @r := 0, @s = NULL,@class=NULL ) temp where subject_name = 'Math' order by  class_num,score desc) temp2;
+------------+-----------+---------+--------+
| dense_rank | class_num | user_id | score  |
+------------+-----------+---------+--------+
|          1 | 001       |       1 | 100.00 |
|          2 | 001       |       3 |  92.00 |
|          3 | 001       |       2 |  89.00 |
|          3 | 001       |      10 |  89.00 |
|          4 | 001       |       7 |  73.00 |
|          1 | 002       |       5 |  98.00 |
|          1 | 002       |       8 |  98.00 |
|          2 | 002       |       9 |  89.00 |
|          3 | 002       |       4 |  87.00 |
|          4 | 002       |       6 |  77.00 |
+------------+-----------+---------+--------+
10 rows in set, 6 warnings (0.00 sec)

3、rank()实现

MySQL8.0

root@mysql 22:33:  [test]> select rank() over w as `rank`,class_num,user_id,score from score where subject_name='Math' window  w as ( order by score desc) ;
+------+-----------+---------+--------+
| rank | class_num | user_id | score  |
+------+-----------+---------+--------+
|    1 | 001       |       1 | 100.00 |
|    2 | 002       |       5 |  98.00 |
|    2 | 002       |       8 |  98.00 |
|    4 | 001       |       3 |  92.00 |
|    5 | 001       |       2 |  89.00 |
|    5 | 002       |       9 |  89.00 |
|    5 | 001       |      10 |  89.00 |
|    8 | 002       |       4 |  87.00 |
|    9 | 002       |       6 |  77.00 |
|   10 | 001       |       7 |  73.00 |
+------+-----------+---------+--------+
10 rows in set (0.00 sec)

Mysql8.0之前

root@mysql 23:12:  [test]> select `rank`,class_num,user_id,score from (SELECT  @r:=if(@s = score,@r,@c)  AS `rank`,@s:=score,@c:=@c+1,class_num,user_id,score from score, ( select @r := 0, @s = NULL,@c:=1 ) r where subject_name = 'Math' order by score desc) temp;
+------+-----------+---------+--------+
| rank | class_num | user_id | score  |
+------+-----------+---------+--------+
|    1 | 001       |       1 | 100.00 |
|    2 | 002       |       5 |  98.00 |
|    2 | 002       |       8 |  98.00 |
|    4 | 001       |       3 |  92.00 |
|    5 | 001       |       2 |  89.00 |
|    5 | 002       |       9 |  89.00 |
|    5 | 001       |      10 |  89.00 |
|    8 | 002       |       4 |  87.00 |
|    9 | 002       |       6 |  77.00 |
|   10 | 001       |       7 |  73.00 |
+------+-----------+---------+--------+
10 rows in set, 5 warnings (0.00 sec)
相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
11天前
|
SQL 关系型数据库 MySQL
MySQL 窗口函数详解:分析性查询的强大工具
MySQL 窗口函数从 8.0 版本开始支持,提供了一种灵活的方式处理 SQL 查询中的数据。无需分组即可对行集进行分析,常用于计算排名、累计和、移动平均值等。基本语法包括 `function_name([arguments]) OVER ([PARTITION BY columns] [ORDER BY columns] [frame_clause])`,常见函数有 `ROW_NUMBER()`, `RANK()`, `DENSE_RANK()`, `SUM()`, `AVG()` 等。窗口框架定义了计算聚合值时应包含的行。适用于复杂数据操作和分析报告。
54 11
|
1月前
|
SQL 关系型数据库 MySQL
MySQL8 窗口函数
MySQL 8 引入了窗口函数,这是一种强大的分析工具,可以在查询结果集中执行计算而无需将数据分组到多个输出行中。本文介绍了窗口函数的基本概念和使用方法,并通过几个实际案例展示了如何使用窗口函数进行成绩和排名统计、销售数据分析等操作。
MySQL8 窗口函数
|
1月前
|
移动开发 关系型数据库 MySQL
MySQL中的ROW_NUMBER窗口函数简单了解下
ROW_NUMBER是 MySQL8引入的窗口函数之一,它为查询结果集中的每一行分配一个唯一的顺序号(行号)。ROW_NUMBER在分页、去重、分组内排序等场景中非常有用。
41 4
|
7月前
|
SQL 关系型数据库 MySQL
MYSQL根据查询结果删除sql 去除重复id 新增对比前一条与后一条数据 去重3种方法​ 窗口函数
MYSQL根据查询结果删除sql 去除重复id 新增对比前一条与后一条数据 去重3种方法​ 窗口函数
147 0
|
6月前
|
关系型数据库 MySQL 数据挖掘
MySQL窗口函数:原理和使用
MySQL窗口函数:原理和使用
|
6月前
|
关系型数据库 MySQL 数据挖掘
MySQL窗口函数详解(概念+练习+实战)
MySQL窗口函数详解(概念+练习+实战)
1246 1
|
6月前
|
关系型数据库 MySQL
MYSQL 窗口函数
MYSQL 窗口函数
|
6月前
|
关系型数据库 MySQL 数据挖掘
MySQL 8窗口函数详解:高效数据处理的必备技能
MySQL 8窗口函数详解:高效数据处理的必备技能
274 3
|
6月前
|
关系型数据库 MySQL Serverless
【随手记】MySQL窗口函数计算累加和
【随手记】MySQL窗口函数计算累加和
519 0
|
7月前
|
SQL Oracle 关系型数据库
MySQL窗口函数怎么用
这段内容介绍了如何使用窗口函数(Window Function)在 MySQL 8.x 中进行更高效、简洁的数据分析和计算。窗口函数允许我们在查询结果集的特定窗口(或分区)内执行计算,如聚合、排序和行号生成。文章以一个复杂的 SQL 查询作为示例,展示了如何使用窗口函数简化这个计算排名的过程。