【MySQL】窗口函数详解(概念+练习+实战)

MySQL 8.0窗口函数详解 推荐场景不推荐场景需要保留原始数据行的分析需求简单求和/计数等基础聚合处理排名、移动平均等复杂逻辑单次查询需要多层嵌套子查询分析数据分布(如累计百分比)对超大数据集且无合适索引时。 阅读详情
AI摘要

本文详细介绍了SQL窗口函数的概念、语法,包括聚合函数和专用函数的使用,以及在LeetCode题目中的实战应用。通过实例学习,展示了如何利用窗口函数解决业务场景问题和性能比较。,

牛逼的兄弟两个月前教了我一招......

前言

2023年12月下旬,广东终于冷了!回想直到12月15那天,依然穿着短袖上班,吹着风扇空调睡觉… 哈哈,这是截至发文时的一些感受与题外话。天气是冷了,但心中依然热情似火,一是工作业务上又有稍微复杂的业务,有挑战;二是虽然有挑战,但想起牛逼的兄弟@CaptinKoo两个月前教了我一招:SQL窗口函数,业务难题迎刃而解!趁着这次解决难题的热度,将本次学到的窗口函数知识点以及项目实战记录下来,供各位分享。

我个人学习窗口函数主要有两个用处:一是对现有SQL知识的拓展,二是能使用窗口函数对一些特定场景做SQL简化,解决复杂问题。

但在正式开始之前,得事先说明一个前提:
前提

  • 窗口函数是 Mysql 8 的新特性。本文的学习与演示,都基于Mysql 8
  • 学习窗口函数,建议有一定的SQL基础

学习目标

  • 学习并了解SQL窗口函数相关概念
  • 能使用SQL窗口函数解决部分业务场景题目,项目实战
  • 若实际业务用得少,那上述知识了解一下即可,建议收藏本文,用到的时候可以翻出来参考

下面我们开始!

1. SQL窗口函数

这一小节我们介绍窗口函数的一些概念。

1.1 窗口函数概念

概念
窗口函数,也叫OLAP函数(Online Anallytical Processing,联机分析处理),可以对数据库数据进行实时分析处理。

窗口函数在MySQL 8中引入,是Mysql 8的新特性。是一种主要用于数据分析、特定字段分组等的一种特殊的函数。

常见使用场景

  • 数据分析,如排名、排序、分组统计、计算、前后值比较等
  • 对某些分组场景简化SQL,提升效率
  • 常用于子查询,将一些复杂条件简化

1.2 窗口函数语法

窗口函数的语法如下:

窗口函数([参数]) OVER (
  [PARTITION BY <分组列>] 
  [ORDER BY <排序列 ASC/DESC>]
  [ROWS BETWEEN 开始行 AND 结束行]
)
  • PARTITION BY 子句用于指定分组列,关键字:PARTITION BY 。
  • ORDER BY 子句用于指定排序列,关键字ORDER BY 。
  • ROWS BETWEEN 子句用于指定窗口的范围,关键字ROWS BETWEEN 即[开始行]、[结束行](这部分在“补充与总结”小节中作补充详细说明)。

其中,ROWS BETWEEN 子句在实际中可能用得相对少一些,因此有部分参考资料的语法描述省略了ROWS BETWEEN 子句,主要侧重于PARTITION BY分组与ORDER BY排序:

窗口函数([参数]) OVER (
  [PARTITION BY <分组列>] 
  [ORDER BY <排序列 ASC/DESC>]

也正因此,本文将ROWS BETWEEN 子句相关关键字知识点将会以补充的形式说明,而侧重常用窗口函数的学习与练习,侧重PARTITION BY 子句与ORDER BY子句的使用。

语法举例,设有Order表,查询销售数量总和及其当前行前两行和后两行的销售数量总和:

SELECT product_id, order_date, quantity,
       SUM(quantity) OVER (PARTITION BY product_id ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS sum_surrounding_quantities
FROM orders

这个例子暂时看不懂个没关系,接下来,我们会详细介绍常见窗口函数,并在介绍的过程中举例。之后,上述例子就很好理解了。

1.3 常见窗口函数

本小节介绍常见窗口函数。

若要跟着本文进行练习,则可以参考着创建如下表,本文的例子均基于下表:

设计一个销售数据表。该表包含以下字段:

  • id :销售记录的唯一标识符(主键)
  • product :产品名称
  • category :产品类别
  • sale_date :销售日期
  • quantity :销售数量
  • revenue :销售收入

以下是创建表的DDL以及 插入模拟数据的DML

CREATE TABLE sales (
  id INT PRIMARY KEY,
  product VARCHAR(50),
  category VARCHAR(50),
  sale_date DATE,
  quantity INT,
  revenue DECIMAL(10, 2)
);

INSERT INTO sales (id, product, category, sale_date, quantity, revenue)
VALUES
  (1, 'Product A', 'Category 1', '2022-01-01', 10, 100.00),
  (2, 'Product B', 'Category 1', '2022-01-01', 5, 50.00),
  (3, 'Product A', 'Category 2', '2022-01-02', 8, 80.00),
  (4, 'Product B', 'Category 2', '2022-01-02', 3, 30.00),
  (5, 'Product A', 'Category 1', '2022-01-03', 12, 120.00),
  (6, 'Product B', 'Category 1', '2022-01-03', 7, 70.00),
  (7, 'Product A', 'Category 2', '2022-01-04', 6, 60.00),
  (8, 'Product B', 'Category 2', '2022-01-04', 4, 40.00);

好的,准备工作完成,下面我们一边学习具体窗口函数并练习吧!

1.3.1 聚合窗口函数

许多窗口函数的教程,通常将常用的窗口函数分为两大类:聚合窗口函数 与 专用窗口函数。聚合窗口函数的函数名与普通常用聚合函数一致,功能也一致。从使用的角度来讲,与普通聚合函数的区别在于提供了窗口函数的专属子句,来使得数据的分析与获取更简便。主要有如下几个:

函数名作用
SUM求和
AVG求平均值
COUNT求数量
MAX求最大值
MIN求最小值

区别
这个例子演示与普通聚合函数的区别。设我们要求使用一条查询语句,在sales表每行最后一列都加上这一行的产品类别 category的 平均 销售收入revenue,并且以category顺序排序,即如下图所示:
00

  • 普通聚合函数的一种解法:
  SELECT 
	t1.*, 
	t2.avg_revenue FROM sales t1 
  LEFT JOIN (
		SELECT category, AVG(revenue) AS avg_revenue 
		FROM sales  
		GROUP BY category
  ) t2 ON t1.category = t2.category ORDER BY t1.category
  • 聚合窗口函数:
SELECT
  sales.*,
  AVG( revenue ) OVER ( PARTITION BY category ) AS avg_revenue 
FROM
	sales

这么一对比,窗口聚合函数简单不少!

1.3.2 专用窗口函数

常见的专用窗口函数

函数名分类说明
RANK排序函数类似于排名,并列的结果序号可以重复,序号不连续
DENSE_RANK排序函数类似于排名,并列的结果序号可以重复,序号连续
ROW_NUMBER排序函数对该分组下的所有结果作一个排序,基于该分组给一个行数
PERCENT_RANK分布函数每行按照公式 (rank-1) / (rows-1) 进行计算
CUME_DIST分布函数分组内小于、等于当前 rank 值的行数 / 分组内总行数

练习
分别对上述表格常见的专用窗口函数进行调用,查看结果。

SELECT 
	*,
	RANK() OVER(PARTITION BY category ORDER BY quantity DESC) AS `quantity_rank`,
	DENSE_RANK() OVER(PARTITION BY category ORDER BY product DESC) AS `product_dense_rank`,
	ROW_NUMBER() OVER(PARTITION BY category ORDER BY product DESC) AS `product_row_number`,
	PERCENT_RANK() OVER(PARTITION BY category ORDER BY quantity DESC) AS `quantity_percent_rank`,
	CUME_DIST() OVER(PARTITION BY category ORDER BY quantity DESC) AS `quantity_cume_dist`
FROM sales

01

至于其它专用窗口函数,请读者自行查阅其它资料做拓展。

1.4 窗口函数性能比较

通过对上面我们对窗口函数的学习与练习,我们一来明白了窗口函数的相关概念、常见窗口函数的使用以及这些窗口函数的作用与效果。也通过窗口函数与一般函数子查询作了一个简单的对比,体现了窗口函数在一些特定需求的强大。那么既然窗口函数如此强大,那么窗口函数的性能对比传统函数、传统子查询与分组的性能相比如何呢?

窗口函数的性能和其它SQL语句一样,受数据量大小、分区复杂度情况等影响。同等数量级的一般情况下:

  • 窗口函数本身内嵌分组,相当于把条件先筛了一遍,可减少部分子查询。减少的子查询部分相当于降低了子查询本身的连接消耗。
  • 窗口函数窗口大小限制,可减少部分行数结果返回消耗。
  • 窗口函数可用于子查询,简化部分语句。但又因为用在了子查询,还是有一定连接开销。
  • 窗口聚合函数在窗口函数原有分区、排序的基础上增加了聚合,且因不会影响行数的关系,比原有分组行数要多,其开销比一般聚合函数开销要大一些,因此窗口聚合函数一般情况下会比普通聚合函数性能差一些。

当然,上述只是理论上的性能初步分析,实际还得视具体的情况而定。
至于窗口函数优化方案,可以以影响窗口函数性能的原因为切入点由因到果进行优化,例如缩小窗口大小限制。篇幅有限,不作详解。详情可参考文末推荐的优秀参考文章。

2. LeetCode 例题

上一小节,我们学习了 SQL 窗口函数的概念,从本小节开始,就是做题练习与实战了!

接下来要列举例题,是 @CaptinKoo 两个月前教我们窗口函数时提供的练习题。让我们跟随 @CaptinKoo 老师的脚步,进行窗口函数练习吧!

2.1 LeetCode SQL 178:分数排名

题目链接
LeetCode-SQL178分数排名链接

题目描述
05

06

题解
根据题目描述,我们得知,返回结果序号可重复,连续,因此我们使用DENSE_RANK()函数。

SELECT 
    score,
    DENSE_RANK() OVER(ORDER BY score DESC) AS `rank`
FROM Scores 
ORDER BY score DESC

2.2 LeetCode SQL 184:最高工资

题目链接
LeetCode-SQL184部门工资最高的员工

题目描述

07
08

题解
根据描述,我们可以通过 RANK 窗口函数对 Employee 表进行排序,获取 rank 值为1 的 员工并关联到部门表。

SELECT 
    d.name AS Department,
    e.name AS Employee,
    e.salary AS Salary
FROM (
    SELECT 
    name, 
    salary,
    departmentId, 
    RANK() OVER(PARTITION BY departmentId ORDER BY salary DESC) AS `rank`
    FROM Employee
) e 
LEFT JOIN Department d 
    ON e.departmentId = d.id
WHERE e.`rank` = 1

2.3 LeetCode SQL 185:前三工资

题目链接
LeetCode-SQL185部门工资前三高的所有员工

题目描述

03

04

题解
有了上面两道题的解题练习,这道题也迎刃而解:

SELECT 
    d.name AS Department,
    e.name AS Employee,
    e.salary AS Salary
FROM (
    SELECT 
    name, 
    salary,
    departmentId, 
    DENSE_RANK() OVER(PARTITION BY departmentId ORDER BY salary DESC) AS `rank`
    FROM Employee
) e 
LEFT JOIN Department d 
    ON e.departmentId = d.id
WHERE e.`rank` <= 3

太棒了!我们一下就完成了三道包括中等、困难难度的LeetCode题目,接下来,我们可以将我们学习并练习过的知识点用于项目实战了!

3. 项目实战

本小节是我个人用窗口函数解决实际工作问题的实战记录。涉及的表、字段均已做描述更换,脱敏处理。

3.1 需求描述

已知用户订单评价表order_evaluate有如下字段:

字段名类型说明
idbigint评价表主键id
evaluatetext评价内容
user_idbigint用户id
update_timedatetime更新时间
order_idbigint订单id

其中,每个订单可以有多个评价,每个评价都可以修改。业务需要,需要获取当前用户所有订单最近一次评价内容,并返回订单id、最近一次评价的内容。

3.2 SQL 实战

此次实战业务需要根据update_time获取最近一次评论并根据order_id进行分组。

在尚未系统学习窗口函数时,我们第一时间会想到的是传统子查询。

但毕竟实际业务远比这里的脱敏描述要复杂,一时间难以实现,于是我第一时间回顾了@CaptinKoo大佬教我的窗口函数并解决:

# 毕竟是Demo,忽略 user_id 条件,实际业务会补充齐全其它条件
SELECT
	t1.order_id,
	evaluate
FROM
	order_evaluate t1
	INNER JOIN ( 
		SELECT id, 
		order_id, 
		ROW_NUMBER() OVER ( PARTITION BY order_id ORDER BY update_time DESC ) AS row_num FROM order_evaluate) t2 ON t1.id = t2.id 
WHERE
	t2.row_num = 1

这条SQL是通过ROW_NUM()函数将工单评价表根据工单分组,更新时间倒序并给它一个行序号。行序号1的就是我们要求的结果。

你能想到用传统子查询实现相同功能的SQL吗?

4. 补充与总结

4.1 ROWS BETWEEN子句常见关键字含义

关键字及其含义表

关键字含义
PRECEDING当前行数往前
FOLLOWING当前行数往后
CURRENT ROW当前行
UNBOUNDED起点(一般结合PRECEDING,FOLLOWING使用)
UNBOUNDED PRECEDING表示该窗口最前面的行(起点)
UNBOUNDED FOLLOWING表示该窗口最后面的行(终点)

此表的知识内容来自于参考文章

根据这个关键字含义表,读者可以理解文初提到的例子了吗?

可选挑战题目
这里提供一题可选的挑战题目链接,是LeetCode困难题目,依然来自@CaptinKoo大佬的推荐,此题的一种解法用到ROWS BETWEEN子句。

LeetCode-601体育馆的人流量

4.2 如何理解窗口函数的“窗口”?

既然这种函数叫"窗口函数",那么它应该可以像"窗口"一样,通过滚动的方式,获取一定范围内的视图。

而滚动的方式恰恰就是ROWS BETWEEN子句。通过ROWS BETWEEN子句,获取窗口函数结果的范围,从而有给用户"窗口"的感觉。

用术语表达,则是:通过定义帧,决定窗口的大小。
窗口函数定义帧通常有两种方式:RANGE和ROWS, 两者决定窗口帧的边界如何计算。

  • RANGE 基于排序列的值定义帧
  • ROWS 基于行数定义帧,不考虑排序列

由于两者用法相似,且一般ROWS BETWEEN 子句会用得多一些,因此本文的语法概述忽略了RANGE子句。此处作为补充,供读者参考。

4.3 总结

本文借由好兄弟@CaptinKoo两个月前教过我的窗口函数知识,截至发文日期顺利解决一个相对比较复杂的业务的故事,记录我从CaptinKoo学到的窗口函数相关知识,以及CaptinKoo大佬推荐的相关习题,以及我个人本次实战的脱敏描述。

通过本文,我们学习到了:

  • Mysql 窗口函数相关概念:其中,语法结构是重点;
  • 常见窗口函数:聚合窗口函数、专用窗口函数(排序函数、分布函数等)
  • 相关习题与练习
  • 一个实际的练习供大佬们参考
  • 窗口函数“窗口”的体现,ROWS BETWEEN子句相关补充知识点

参考资料

再次感谢@CaptinKoo的指导!

MySQL的窗口函数介绍 MySQL窗口函数是8.0版本的重要特性,允许在不减少行数的情况下进行数据分析。主要包括三类函数:排名函数(RANK、DENSE_RANK、ROW_NUMBER)、分析函数(LEAD/LAG、FIRST_VALUE/LAST_VALUE)和聚合函数。窗口定义包含PARTITION BY分组、ORDER BY排序和窗口框架设置。高级用法包括多窗口定义、百分比计算、分组TopN查询等。使用窗口函数时需注意大数据集的性能问题。窗口函数将复杂的数据分析计算高效地转移到数据库层完成。 阅读详情

相关推荐

MySQL 窗口函数

MySQL窗口函数是一种强大的工具,用于在查询中执行复杂的统计分析,而不需要改变表的结构或数据。MySQL从8.0版本开始支持窗口函数,这些函数也被称为分析函数,因为它们能够处理相对复杂的报表统计分析场景。窗口的意思是将数据进行分组,每个分组即是一个窗口,这和使用聚合函数时的group by分组类似,但与聚合函数不同的地方是: 聚合函数(例如:sum/avg/min/max)会针对每个分组(窗口)聚合出一个结果(每一组返回一个结果)。

m0_74825466的博客 1689

MySQL窗口函数(MySQL Window Functions)

百分比排序,返回当前行在组内的百分比位置,返回值范围为[0, 1],可以用 当前行排序/(行数-1) 计算得出,但与rank不同,这里排序是从0开始而不是从1(相当于rank-1),因此第1行的百分比位置是0%,相当于:(rank-1)/(rows-1)。没有参数,返回当前行在组内的位置编号,从1开始,order by子句会影响行的编号顺序,如果没有order by,那么行的编号是不确定的。另外,即使行完全相同,它们的编号也是不同的,这点和后面的 rank() 和 dense_rank() 不同。

最简单的方法,解决最实际的问题。 1万+

MySQL中的窗口函数

窗口函数允许用户在查询结果集的一个子集上执行计算,这个子集被称为“窗口”。窗口函数通常与OVER()子句一起使用,以定义窗口的范围和顺序。它们可以用于计算聚合值(如总和、平均值等),也可以用于排名和排序。:为窗口中的每一行分配一个唯一的序号。RANK()和:为窗口中的每一行分配一个排名。LEAD()和LAG():访问结果集中当前行的前一行或后一行的值。SUM()AVG()MIN()MAX()等聚合函数:在窗口上执行聚合计算。

Meteor的笔记 1127

【数据库高级】Mysql窗口函数的使用和练习

搞清楚窗口代表着啥,才知道什么时候该用它。窗口函数是相对于聚函数来说的。聚合函数是对一组数据计算后返回单个值(即分组)。非聚合函数一次只会处理一行数据。而窗口函数在行记录上计算某个字段的结果时,可将窗口范围内的数据输入到聚合函数中,并不改变行数。根据上面所说,准备如下员工表信息数据。

D_AKJS 5824

FPGA设计中的扇出约束实战:从XDC到代码优化的全流程解析

本文全面解析FPGA设计中扇出约束的重要性与应用,详细介绍了从XDC约束文件设置到HDL代码优化的全流程实战技巧。通过MAX_FANOUT约束控制信号负载,结合寄存器复制和物理优化策略,有效解决高扇出导致的时序违例和布线拥塞问题,提升设计性能和可靠性。

nept的博客 797

学习笔记( 11 ) - 解决:finalshell连接虚拟机(ubuntu)超时,主机ping不通虚拟机,主机没有VMnet8网卡, Connection refused: connect

解决:finalshell连接虚拟机(ubuntu)超时,主机ping不通虚拟机,主机没有VMnet8网卡, Connection refused: connect

烟溪彭于晏的博客 2万+

MySQL 一定要会的窗口函数

MySQL 窗口函数(Window Functions) 是 MySQL 8.0 引入的一项强大功能,主要用于数据分析。它们允许在查询结果的某些行上执行计算,并返回这些行,同时保留其各自的行。窗口函数非常适合排名、累计和移动平均等操作。

zk1147756506的博客 1598

关于Fluent模拟中的动态接触角UDF实现,详细设计

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1147

Mysql -窗口函数详解

数据分析|MySQL窗口函数详解与案例实战 1. 窗口函数描述 含义:窗口函数也叫OLAP函数(Online Anallytical Processing,联机分析处理),可对数据进行实时分析处理。 作用:解决排名问题,e.g.每个班级按成绩排名;解决TOPN问题,e.g.每个班级前两名的学生 语法: <窗口函数> over ([partition by <列清单>] order by <排序用列清单>) .

cjx_cqupt的博客 2412

cuRobo 入门教程(一)基于CUDA的机器人运动规划加速库

cuRobo是一款基于CUDA加速的机器人算法库,提供运动学计算、碰撞检测、轨迹优化等功能。该库采用并行优化技术,在GPU上实现高效运算,比现有方案快60倍。cuRobo支持多种机器人模型和环境表示,包含正向/逆向运动学、无碰撞轨迹生成、模型预测控制等核心算法,并可与IsaacSim等仿真平台集成。其特点包括:1) 支持多种碰撞检测方式(长方体/网格/深度图像);2) 提供批量环境处理和实时世界更新;3) 通过CUDA Graphs加速优化迭代;4) 在Jetson Orin等平台实现毫秒级运动规划。安装支

2506_90492529的博客 1931

14、X射线图像处理与特征提取全解析

本文全面解析了X射线图像处理与特征提取的关键技术,涵盖图像预处理、滤波、边缘检测、分割与恢复等流程,并深入探讨了几何特征(如面积、周长、圆度、椭圆拟合)、强度特征(均值、方差、对比度)、傅里叶描述符、不变矩(Hu矩、Gupta矩)以及局部描述符(SIFT、SURF)和稀疏表示方法。同时介绍了特征选择的过滤法、包装法和嵌入法,并结合Matlab代码示例展示了各类特征的实际提取过程。文章最后总结了当前技术的应用现状,并展望了深度学习在X射线图像分析中的未来潜力。

pz89012345的博客 134
上一篇: 【计算机基础】程序猿需要了解的存储器知识
下一篇: 【工具与中间件】通过飞书应用操作云文档
CoderSharry
博客等级 码龄4年 515粉丝 69原创
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值