MySQL8新特性:窗口函数

目录

  • 一、 概念
  • 二、语法
    • 基本语法
    • 语法解析
    • 进阶语法
      • 命名窗口WINDOW AS
      • 框架FRAME_CLAUSE
  • 三、窗口函数
    • ROW_NUMBER
    • RANK
    • DENSE_RANK
    • PERCENT_RANK
    • CUME_DIST
    • FIRST_VALUE与LAST_VALUE
    • NTH_VALUE
    • LAG与LEAD
    • NTILE
  • 四、窗口函数使用事项
  • 五、窗口函数优化方法
  • 六、面试常问(持续更新)

本文将介绍MySQL8的新特性——窗口函数,从概念、语法、示例等各方面详细解析窗口函数,以及解析面试中窗口函数常考的题目和知识点。
窗口函数(Windows Function),也被称为分析函数(Analytic Functions),在Oracle中使用已久,但直到MySQL8才开始引入。窗口函数非常强大,在工作中应用非常广泛,专门用于处理复杂的统计分析问题,例如移动平均、累计总和、百分比排名等,它可直接添加新的聚合字段,而不必在数据库应用程序级别编写额外的代码。

环境:MySQL 8.0.32

下文语法中[]包括的内容表示可选项

以下示例均用下表以及下表数据进行演示:

-- 创建一个销售表(sales),其中包含销售日期(sale_date)、销售员ID(sale_id)和销售额(sale_amount)
CREATE TABLE sales (  id INT AUTO_INCREMENT PRIMARY KEY,  sale_date DATE,  sale_id INT,  sale_amount DECIMAL(10, 2)  
);  -- 插入一些示例数据  
INSERT INTO sales (sale_date, sale_id, sale_amount) VALUES  
('2023-01-01', 1, 1000.00),  
('2023-01-02', 1, 1200.00),  
('2023-01-03', 2, 800.00),  
('2023-01-03', 1, 1000.00),  
('2023-01-05', 2, 900.00),  
('2023-01-01', 3, 700.00),  
('2023-01-02', 3, 750.00),  
('2023-01-03', 4, 600.00),  
('2023-01-04', 3, 800.00),  
('2023-01-05', 4, 650.00);

一、 概念

窗口函数会对数据进行分组,每个分组即为一个窗口。和分组聚合不同,窗口函数会对窗口中每一条记录进行应用,不会使记录变少(每一行返回一个结果),其重点是当前行与窗口以及窗口中其他行的联系。

二、语法

基本语法

SELECT WINDOW_FUNCTION() OVER([PARTITION BY `字段1`, `字段2`, ...][ORDER BY `字段3`, `字段4`][FRAME_CLAUSE]) [AS `别名`]
FROM `表名` ;

注意: 窗口函数可使用别名

语法解析

WINDOW_FUNCTION
MySQL8支持以下几类窗口函数:

  • 序号函数:用于为窗口内的每一行生成一个序号,例如 ROW_NUMBER(),RANK(),DENSE_RANK() 等。
  • 分布函数:用于计算窗口内的每一行在整个分区中的相对位置,例如 PERCENT_RANK(),CUME_DIST() 等。
  • 前后函数:用于获取窗口内的当前行的前后某一行的值,例如 LAG(),LEAD() 等。
  • 头尾函数:用于获取窗口内的第一行或最后一行的值,例如 FIRST_VALUE(),LAST_VALUE() 等。
  • 聚合函数:用于计算窗口内的某个字段的聚合值,例如 SUM(),AVG(),MIN(),MAX() 等。

OVER
OVER关键字用于标识是否使用窗口函数,有两种用法:

  • OVER(),常规用法,窗口规范放在括号中。如窗口规范为空,表示没有窗口划分,默认所有数据为一组。
  • OVER window_name:由FROM后的WINDOW子句定义窗口规范,可重复使用。下文进阶语法-命名窗口中会进行详细解析。

PARTITION BY
用于将记录划分为不同的分区,窗口函数在每个分区上分别执行。可以使用多个字段进行分区。

ORDER BY
用于将每个分区中的记录进行排序,窗口函数将按照排序后的顺序进行计算。可使用多个字段进行排序,默认ASC(升序)。

FRAME_CLAUSE
框架子句,用于指定每个分区的数据范围。下文进进阶语法-框架中会进行详细解析

进阶语法

命名窗口WINDOW AS

有时候多次重复定义或引用相同的窗口,很繁琐。为了避免重复定义,减少语句的繁琐,可以在WINDOW子句中定义并命名窗口,并在OVER中通过窗口名进行引用。一次定义,多次引用。
语法定义如下:

SELECT  WINDOW_FUNCTION() OVER `窗口名`|(`窗口名` ...)
FROM `表名` 
WINDOW `窗口名` AS ()

说明:

  • WINDOW子句中的括号内的部分就是原OVER子句后的窗口定义。使用OVER关键字调用窗口时,可直接引用窗口名或者可对窗口进行进一步的加工,使用括号括起来并在其中使用其他的窗口规范。
  • 一个命名窗口的定义本身也可以以一个窗口名开头。这样可以实现窗口之间的引用,但不能形成循环。

示例:

-- 查询每个销售员最早和最晚的销售日期
SELECT DISTINCT `sale_id`,FIRST_VALUE(`sale_date`) OVER (w ORDER BY `sale_date` DESC) first_date_desc,FIRST_VALUE(`sale_date`) OVER w first_date_asc
FROM `salse`
WINDOW w AS (PARTITION BY `sale_id`); 
-- 定义了三个命名窗口,其中第二个和第三个都引用了第一个,这样每个窗口就可以根据不同的范围来计算累计和
SELECT sale_amount,SUM(`sale_amount`) OVER w1 AS sum_amount1,SUM(`sale_amount`) OVER w2 AS sum_amount2,SUM(`sale_amount`) OVER w3 AS sum_amount3
FROM `salse`
WINDOW w1 AS (ORDER BY `sale_amount`),w2 AS (w1 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),w3 AS (w1 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW);

框架FRAME_CLAUSE

框架是窗口的一个子集,通过框架子句可以显式定义框架的范围,指定每个分区内应用窗口函数的一小部分数据的区域。可根据当前行动态的变化,或者选定一个静态的范围。
frame_clause由两个子句组成,语法定义如下:

frame_units frame_extent

解析
frame_units(框架单位)指定框架使用的单位,有两种:

  • ROWS:基于行数,通过起始行和结束行来划定框架的范围,边界是明确的一行。

  • RANGE:基于值的大小,通过具有相同值的行来划定框架的范围,边界是一个范围,具有相同值的行作为一个整体看待。

frame_extend(框架范围)指定窗口范围的起始位置和结束位置,可通过BETWEEN frame_start AND frame_end子句定义,frame_start和frame_end有以下几种形式:

  • CURRENT ROW:当前行
  • UNBOUNDED PRECEDING:分区中第一行
  • UNBOUNDED FOLLOWING:分区中最后一行
  • expr PRECEDING:当框架单位是ROWS时, 边界是当前行的前expr行。当框架单位是RANGE时,边界是值和"当前行的值减去expr"相等的行,可能有多个,统一视为一个边界。
  • expr FOLLOWING:当框架单位是ROWS时, 边界是当前行的后expr行。当框架单位是RANGE时,边界是值和"当前行的值加上expr"相等的行,可能有多个,统一视为一个边界。

以下为框架范围图示:
图示

注意:

  • 含有ORDER BY子句,框架从组内第一行到当前行
  • 不含ORDER BY子句,框架从组内第1行到最后一行(组内所有行)
  • 如果没有指定frame_clause,则默认为RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,即从分区开始到与当前行值相同的行。
  • 如果没有定义框架的终点,则默认终点为CURRENT ROW当前行

示例:

SELECT `sale_id`, `sale_amount`,SUM(`sale_amount`) OVER (PARTITION BY `sale_id` ORDER BY `sale_amount` ROWS 1 PRECEDING) AS s1,SUM(`sale_amount`) OVER (PARTITION BY `sale_id` ORDER BY `sale_amount` RANGE 1 PRECEDING) AS s2,SUM(`sale_amount`) OVER (PARTITION BY `sale_id` ORDER BY `sale_amount` RANGE 100 PRECEDING) AS s3
FROM `sales`;

结果如下:
在这里插入图片描述
以上示例中:

  • s1框架单位为ROWS,框架范围为1 PRECEDING,起点边界为当前行的前一行,终点默认为当前行。
  • s2框架范围为RANGE,框架范围为1 PRECEDING,起点边界为“当前行的值减去1”,终点默认为当前行。但是值差距都比1大,起点都无法触及之前的行,所以每行的框架都只包含当前行。
  • s3框架范围为RANGE,框架范围为100 PRECEDING,起点边界为“当前行的值减去100”,终点默认为当前行,即取每个分区中值的范围在区间[当前行值-100,当前行值]内的所有行
    • sale_id为1的分组中,第2行的sale_amount为1000,因此框架包含值在[1000-100, 1000]范围内的所有行,即第1,2行,sum求和结果为2000。其他分组同理。
    • sale_id为1的分组中,第3行的sale_amount为1200,因此框架包含值在[1200-100, 1200]范围内的所有行,只有第3行,则sum求和结果为其本身的值。

三、窗口函数

聚合函数在窗口中使用与分组中相同,不做详细解析。以下介绍的专用窗口函数必须搭配OVER关键字使用。

ROW_NUMBER

无参数,返回当前行在分组内的序号,从1开始,每一行都有唯一的序号。
如果无ORDER BY子句,行的编号不确定。

RANK

无参数,返回当前行在分组内的序号,排序带间隙(序号不连续),值相同,序号相同。
可使用ORDER BY子句指定按某列进行排序。如无ORDER BY子句,则所有行的序号都为1

DENSE_RANK

无参数,返回当前行在分组内的排序,排序不带间隙(序号连续),值相同,序号相同。

示例:

SELECT `sale_id`, `sale_amount`,ROW_NUMBER() OVER (PARTITION BY `sale_id` ORDER BY `sale_amount`) AS row_number排序,RANK() OVER (PARTITION BY `sale_id` ORDER BY `sale_amount`) AS rank排序,DENSE_RANK() OVER (PARTITION BY `sale_id` ORDER BY `sale_amount`) AS dense_rank排序
FROM `sales`;

结果如下:
在这里插入图片描述

PERCENT_RANK

百分比排序,返回当前行在分组内的百分比位置,返回值范围为[0, 1],第1行的百分比位置是0%。
计算公式:(rank-1)/(rows-1),rank为RANK函数返回的序号,rows为组内总行数
示例:

SELECT `sale_id`,`sale_amount`,RANK() OVER (PARTITION BY `sale_id` ORDER BY `sale_amount`) rank排序PERCENT_RANK() OVER (PARTITION BY `sale_id` ORDER BY `sale_amount`) percent_rank排序
FROM `sales`;

结果如下:
在这里插入图片描述
以上示例中:sale_id为1的分组中,第3行RANK排序为3,组内总行数为3,所以PRECENT_RANK为(3-1)/(3-1)=1。其他同理。

CUME_DIST

累积分布(cumulative distribution),返回分组中值的累积分布,数据分布从0到1。
计算公式:(值小于等于当前行的行数)/分组内总行数。
示例:

SELECT`sale_id`,`sale_amount`,CUME_DIST() OVER (PARTITION BY `sale_id` ORDER BY `sale_amount`) 累积分布
FROM `sales`;

结果如下:
在这里插入图片描述
以上示例中:

  • sale_id为1的分组中,小于等于第1行值的行为第1、2行,所以2/3=0.666…。第2行同理。
  • sale_id为2的分组中,小于等于第4行值的行只有第4行,所以1/2=0.5。而小于等于第5行的值的行有第4、5行,所以2/2=1。其他分组同理。

FIRST_VALUE与LAST_VALUE

FIRST_VALUE(offset):返回当前框架对应参数第一行的值,参数expr为要检索的列或表达式,根据排序定义不同而变化。
LAST_VALUE(offset):返回当前框架对应参数最后一行的值,参数expr为要检索的列或表达式,根据排序定义不同而变化。

NTH_VALUE

NTH_VALUE(offset, N):返回当前框架第N行对应参数的值,没有时返回NULL。参数expr为要检索的列或表达式,参数offset为第几行。
用得较少,可以根据以上示例自己尝试练习。

LAG与LEAD

LAG(expr [, offset [, default]]):返回当前框架内当前行之前的某一行的值。参数expr为要检索的列或表达式,参数offset为要返回的行数,如果不指定,默认为1,即上一行。参数default为当指定的行数超出结果集范围时,返回的默认值。
LEAD(expr [, offset [, default]]):返回当前框架内当前行之后的某一行的值。参数expr为要检索的列或表达式,参数offset为要返回的行数,如果不指定,默认为1,即下一行。参数default为当指定的行数超出结果集范围时,返回的默认值。
示例1:

SELECT`sale_id`,`sale_amount`,LAG(`sale_amount`*2) OVER w 当前行前1行乘2,LAG(`sale_amount`,1,'不存在') OVER w 当前行前1行带默认值,`sale_amount`-LAG(`sale_amount`,2) OVER w 当前值与前2行的差
FROM `sales`WINDOW w AS (PARTITION BY `sale_id` ORDER BY `sale_amount`);

结果如下:
在这里插入图片描述

示例2:

SELECT`sale_id`,`sale_amount`,LEAD(`sale_amount`*2) OVER w 当前行后1行乘2,LEAD(`sale_amount`,1,'不存在') OVER w 当前行后1行带默认值,`sale_amount`-LEAD(`sale_amount`,1) OVER w 当前值与后1行的差
FROM `sales`WINDOW w AS (PARTITION BY `sale_id` ORDER BY `sale_amount`);

结果如下:
在这里插入图片描述

NTILE

NTILE(N):分区内有序数据根据不同等级分为N个桶,记录桶编号。参数为子分区数量。
用得较少,可以根据以上示例自己尝试练习。

四、窗口函数使用事项

  • 窗口函数可以在不改变原表行数的情况下,对每个分区内的查询行进行聚合、排序、排名等操作,提高了数据分析的灵活性和效率。
  • 窗口函数可以使用滑动窗口来处理动态的数据范围。
  • 窗口函数可以与普通聚合函数、子查询等结合使用,实现更复杂的查询逻辑。
  • 窗口函数的语法较为复杂,需要注意OVER子句中的各个参数的含义和作用。
  • 窗口函数的执行效率可能不如普通聚合函数,因为它需要对每个分区内的每个查询行进行计算,而不是折叠为单个输出行。但一般来说,窗口函数的性能优于使用子查询或连接的方法,因为窗口函数只需要扫描一次数据,而子查询或连接可能需要多次扫描或连接。
  • 窗口函数只能在SELECT列表和ORDER BY子句中使用,不能用于WHERE、GROUP BY、HAVING等子句中。

五、窗口函数优化方法

  • 选择合适的窗口函数,避免使用复杂或重复的窗口函数。
  • 使用Named Windows来定义和引用窗口,避免在多个OVER子句中重复定义相同的窗口。
  • 尽量减少分区和排序的代价,使用索引或物化视图来加速分区和排序。
  • 尽量减少窗口的大小,使用合适的frame_clause来限制窗口内的数据范围。
  • 尽量使用并行处理来加速窗口函数的计算,利用多核或分布式系统来提高效率。

六、面试常问(持续更新)

1.序号函数:row_number(),rank(),dense_rank()的区别
ROW_NUMBER,顺序排序——1、2、3
RANK,并列排序,跳过重复序号——1、1、3
DENSE_RANK,并列排序,不跳过重复序号——1、1、2

2.求连续登录3天的用户
解题思路:
①同一用户不可能在同一时间登录,不可能出现相同的登录时间,所以可以使用窗口函数根据用户进行分组,再根据登录时间进行排序,窗口函数可使用任一排序函数。
②如果连续登录,那么登录时间减去序号所获得的日期相同。
③统计获得的相同日期的个数,等于3的即为所求

解题过程(使用以上数据集):
根据sale_id分区,根据sale_date排序,DATA_SUB函数在sale_date日期上减去序号的天数获得data_flag。

SELECT `sale_id`,`sale_date`,`rn`,DATE_SUB(`sale_date`, INTERVAL `rn` DAY) AS `date_flag`
FROM (SELECT *,ROW_NUMBER() OVER (PARTITION BY `sale_id` ORDER BY `sale_date` ASC) AS `rn`FROM `sales`
) tmp

在这里插入图片描述
最终答案(使用以上数据集):
根据sale_id分区排序后,分组统计登录时间减去序号获得的相同日期的个数,等于3即为连续登录3天的用户。

SELECT `sale_id`,COUNT(*) AS`days_count`
FROM (SELECT *,ROW_NUMBER() OVER (PARTITION BY `sale_id` ORDER BY `sale_date` ASC) AS `rn`FROM `sales`) tmp
GROUP BY `sale_id`, DATE_SUB(`sale_date`, INTERVAL `rn` DAY)
HAVING days_count=3

在这里插入图片描述


如有问题和建议,可私信或评论,非常感谢。

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

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

相关文章

设计模式-创建型-04-建造者模式

1、盖房项目需求 1)需要建房子:这一过程为打桩、砌墙、封顶2)房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的3)请编写程序&#xf…

【1990-2023】上市公司高新技术企业数据(Excel+stata)+do代码

数据简介:根据《上市公司资质认定信息文件》 数据进行整理。筛选“认定项目类型” 为“高新技术企业”;筛选“认定对象身份”为“上市公司本身”,根据“认定时间”和“有效期限”判断当年是否为高新技术企业。有效期限通常为3年,缺…

华为手环9省电设置

1、 手环开启熄屏显示续航约3天,原因为屏幕持续常亮显示;如不需要可通过手环“设置->表盘->熄屏显示”路径进行关闭; 2、 手环具备后台健康自动检测功能,您可根据需要选择是否使用或关闭: (1&#x…

实用软件分享-----一款免费的人工智能替换face的神器

专栏介绍:本专栏主要分享一些实用的软件(Po Jie版); 声明1:软件不保证时效性;只能保证在写本文时,该软件是可用的;不保证后续时间该软件能一直正常运行;不保证没有bug;如果软件不可用了,我知道后会第一时间在题目上注明(已失效)。介意者请勿订阅。 声明2:本专栏的…

DoIP——step2:车辆发现

文章目录 前言一、IP地址配置1.1 AutoIP1.2 DHCP1.3 DoIP实体的IP地址配置流程二、车辆发现车辆声明报文内容如下:前言 完成诊断设备到车辆的物理连接并通过激活线使能诊断连接后边缘节点将会将连接状态传递至应用层,在开始车辆发现过程之前,需要先进行各自的IP地址配置,获…

海外媒体发稿渠道和方法有哪些?如何选择靠谱的国外媒体发稿服务商?

在选择海外媒体发稿服务商时,以下是一些关键点可以帮助您找到靠谱的服务商: 服务商的经验和口碑:查找该服务商在行业内的声誉和客户评价。拥有丰富经验和良好口碑的服务商通常更可靠。 媒体资源和覆盖范围:了解服务商所能提供的媒…

Python并行编程技术与方法详解:线程池、进程池及优化策略

目录 一、引言 二、线程池 线程池的概念 Python中的线程池实现 线程池的优缺点 三、进程池 进程池的概念 Python中的进程池实现 进程池的优缺点 四、优化策略 合理设置线程池和进程池的大小 任务的拆分与合并 使用队列和锁等同步机制 选择合适的并行框架 五、异…

【机器学习300问】121、RNN是如何生成文本的?

当RNN模型训练好后,如何让他生成一个句子?其实就是一个RNN前向传播的过程。通常遵循以下的步骤。 (1)初始化 文本生成可以什么都不给,让他生成一首诗。首先,你需要确定采样的起始点。这可以是一个特殊的开…

MySQL-DDL(Data Definition Language)

078-对表结构进行增删改操作 增删改表结构DDL(Data Definition Language) 创建一个学生表 create table t_student( no bigint, name varchar(255), age int comment 年龄 );查看建表语句 show create table t_student;修改表名 alter table 表名 r…

Linux网络 - HTTP协议

文章目录 前言一、HTTP协议1.urlurl特殊字符 requestrespond 总结 前言 上一章内容我们讲了在应用层制定了我们自己自定义的协议、序列化和反序列化。 协议的制定相对来讲还是比较麻烦的,不过既然应用层的协议制定是必要的,那么肯定已经有许多计算机大佬…

C++使用spdlog输出日志文件

参考博客: 日志记录库 spdlog 基础使用_spdlog 写日志-CSDN博客 GitHub - gabime/spdlog: Fast C logging library. 首先在github上下载spdlog源码,不想编译成库的话,可以直接使用源码,将include文件夹下的spdlog文件夹&#x…

示例:WPF中TreeView自定义TreeNode泛型绑定对象

一、目的&#xff1a;在开发中经常需要绑定TreeView&#xff0c;所以定义了一个泛型的TreeNode<T>用来方便绑定对象和复用 二、实现 public partial class TreeNodeBase<T> : SelectBindable<T>, ITreeNode{public TreeNodeBase(T t) : base(t){}private Obs…

MySQL常见的命令

MySQL常见的命令 查看数据库&#xff08;注意添加分号&#xff09; show databases;进入到某个库 use 库; 例如&#xff1a;进入test use test;显示表格 show tables;直接展示某个库里面的表 show tables from 库&#xff1b; 例如&#xff1a;展示mysql中的表格 show tabl…

前端三大件速成 05 javascript(2)字符串对象、数组对象、函数对象、BOM对象、DOM对象

文章目录 一、字符串对象1、创建字符串对象的两种方式2、字符串属性3、字符串的方法&#xff08;1&#xff09;编排方法&#xff08;2&#xff09;查询字符串索引&#xff08;3&#xff09;字符串切片&#xff08;4&#xff09;大小写转换&#xff08;5&#xff09;获取指定字符…

Python网络数据抓取(9):XPath

引言 XPath 是一种用于从 XML 文档中选取特定节点的查询语言。如果你对 XML 文档不太熟悉&#xff0c;XPath 可以帮你完成网页抓取的所有工作。 实战 XML&#xff0c;即扩展标记语言&#xff0c;它与 HTML&#xff0c;也就是我们熟知的超文本标记语言&#xff0c;有相似之处&am…

pyqt 鼠绘椭圆 椭圆标注

目录 pyqt 椭圆标注 四个方向可以调整,调整时,另一端固定,只调整当前端,椭圆参数保存加载json pyqt 画椭圆中心点固定,调整是,两端一起调整。 pyqt 椭圆标注 四个方向可以调整,调整时,另一端固定,只调整当前端,椭圆参数保存加载json import sys import json from …

ClickHouse快速安装教程(MacOS)

文章目录 ClickHouse快速安装教程&#xff08;MacOS&#xff09;1.ClickHouse2.快速安装3.快速启动3.1.启动服务器3.2.启动客户端 4.使用案例1.配置文件2.启动CK服务3.创建数据库4.创建表5.插入数据6.查询数据 ClickHouse快速安装教程&#xff08;MacOS&#xff09; 1.ClickHo…

【UEditorPlus】后端配置项没有正常加载,上传插件不能正常使用

接上文【UEditorPlus】后端配置项没有正常加载&#xff0c;上传插件不能正常使用_ueditor ruoyi vue后端配置项没有正常加载,上传插件不能正常使用!-CSDN博客 成功解决图片、视频上传问题后&#xff0c;当服务部署在公网时&#xff0c;会存在大文件无法正常上传的问题。 出现…

电感的本质是什么

什么是电感&#xff1f; 电感器件一般是指螺线圈&#xff0c;由导线圈一圈靠一圈地绕在绝缘管上&#xff0c;绝缘管可以是空心的&#xff0c;也可以包含铁芯或磁粉芯。 为什么把’线’绕成’圈’就是电感&#xff1f; 电感的工作原理非常抽象&#xff0c;为了解释什么是电感…

04通讯录管理系统——退出功能实现

功能描述&#xff1a;退出通讯录 思路&#xff1a;根据用户不同的选项&#xff0c;进入不同的功能&#xff0c;可以选择switch分支结构&#xff0c;将整个架构进行搭建 当用户选择0时&#xff0c;执行退出&#xff0c;选择其他时先不做操作&#xff0c;也不退出程序 代码&am…