SQL中的窗口函数

1.窗口函数简介

窗口函数是SQL中的一项高级特性,用于在不改变查询结果集行数的情况下,对每一行执行聚合计算或者其他复杂的计算,也就是说窗口函数可以跨行计算,可以扫描所有的行,并把结果填到每一行中。这些函数通常与OVER()子句一起使用,可以定义窗口或分区,并在上面执行计算,使用窗口函数,可以使许多难以处理的棘手问题变得较为容易。
窗口函数的特点包括:

  1. 输入多行(一个窗口),返回一个值:窗口函数为每行数据进行一次计算,但不会改变原始查询结果集的行数
  2. 计算方式灵活:可以使用partition by字句将数据分区,并使用order by子句来进行排序等一些复杂运算
  3. 与聚合函数结合使用:可以与聚合函数结合使用,在不分组的情况下计算如总和、平均值、最小值、最大值等聚合值。

2.语法结构解析

<窗口函数> OVER ([PARTITION BY <分组列>][ORDER BY <排序列>][ROWS 或 RANGE <窗口框架定义>]
)

其中:

  • PARTITION BY 子句用于将数据分成不同的分区,窗口函数将在每个分区内执行。可以理解为group by
  • ORDER BY 子句定义了数据的排序方式,决定窗口函数的计算顺序。
  • ROWS BETWEEN 子句指定了窗口的范围,可以是行数、区间等。

3.常用的窗口函数SQL示例

常用的窗口函数有:

  1. 聚合函数SUM()AVG()COUNT()MAX()MIN()
  2. 排名函数
    • ROW_NUMBER():为窗口内的每一行分配一个唯一的序号,序号连续且不重复;
    • RANK():排名函数,允许有并列的名次,名次后面会出现空位。
    • ENSE_RANK():排名函数,允许有并列的名次,名次后面不会空出位置,即序号连续。
  3. 分组窗口函数
    • NTILE():将窗口内的行分为指定数量的组,每组的行数尽可能相等。
  4. 分布窗口函数
    • PERCENT_RANK():计算每一行的相对排名,返回一个介于0到1之间的值,表示当前行在分区中的排名百分比。
    • CUME_DIST():计算小于或等于当前行的行数占窗口总行数的比例。
  5. 取值窗口函数
    • LAG():访问当前行之前的第n行数据。
    • LEAD():访问当前行之后的第n行数据。
    • FIRST_VALUE():获取窗口内第一行的值。
    • LAST_VALUE():获取窗口内最后一行的值。
    • NTH_VALUE():获取窗口内第n行的值,如果存在多行则返回第一个。

这里以employees表为例:

CREATE TABLE employees (employee_id INT PRIMARY KEY,name VARCHAR(255),department_name VARCHAR(255),salary DECIMAL(10, 2)
);-- 插入数据
INSERT INTO employees (employee_id, department_name, name, salary) VALUES
(1, '财务部', '张三', 30000),
(2, '财务部', '李四', 25000),
(3, '市场部', '王五', 40000),
(4, '市场部', '赵六', 35000),
(5, '市场部', '孙七', 50000),
(6, '技术部', '周八', 45000),
(7, '技术部', '钱九', 60000),
(8, '技术部', '吴十', 55000);
聚合窗口函数查询
SELECTemployee_id,name,department_name,salary,SUM(salary) OVER (PARTITION BY department_name) AS total_salary,AVG(salary) OVER (PARTITION BY department_name) AS average_salary,COUNT(*) OVER (PARTITION BY department_name) AS employee_count,MAX(salary) OVER (PARTITION BY department_name) AS max_salary,MIN(salary) OVER (PARTITION BY department_name) AS min_salary
FROM employees;

执行输入如下:
在这里插入图片描述

排名窗口函数查询
ROW_NUMBER()窗口函数查询
SELECTemployee_id,name,department_name,salary,ROW_NUMBER() OVER () AS salary_rank
FROM employees;

执行输出如下:
在这里插入图片描述
如果想要在部门内分区添加行号

SELECTemployee_id,name,department_name,salary,ROW_NUMBER() OVER (PARTITION BY department_name ORDER BY salary DESC) AS salary_rank
FROM employees;

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

RANK() && DENSE_RANK()

RANK()DENSE_RANK() 函数的主要区别在于处理并列名次的方式RANK() 函数在遇到并列名次时会在下一个名次处留出空位,而 DENSE_RANK() 函数则不会留出空位,名次连续。
我们需要确保每个部门至少有两个员工的薪资是相同的。我们假如插入的数据如下:

INSERT INTO employees (employee_id, name, department_name, salary) VALUES
(1, 'Alice', '财务部', 70000),
(2, 'Bob', '财务部', 60000),
(3, 'Charlie', '财务部', 60000), -- 与Bob薪资相同
(4, 'David', '市场部', 80000),
(5, 'Eve', '市场部', 80000), -- 与David薪资相同
(6, 'Frank', '市场部', 50000),
(7, 'Grace', '技术部', 90000),
(8, 'Heidi', '技术部', 75000),
(9, 'Ivan', '技术部', 75000), -- 与Heidi薪资相同
(10, 'Judy', '财务部', 60000), -- 与Bob和Charlie薪资相同
(11, 'Karl', '市场部', 50000), -- 与Frank薪资相同
(12, 'Linda', '技术部', 90000), -- 与Grace薪资相同
(13, 'Mike', '财务部', 50000), -- 新薪资水平
(14, 'Nancy', '市场部', 60000), -- 与Bob和Charlie薪资相同
(15, 'Oliver', '技术部', 60000); -- 与Bob和Charlie薪资相同

执行包含 RANK()DENSE_RANK() 函数的查询:

SELECTemployee_id,name,department_name,salary,RANK() OVER (PARTITION BY department_name ORDER BY salary DESC) AS salary_rank,DENSE_RANK() OVER (PARTITION BY department_name ORDER BY salary DESC) AS dense_salary_rank
FROM employees
ORDER BY department_name, salary DESC;

执行结果如下:
在这里插入图片描述
在这个结果中,以财务部为例,

  • Alice 薪资最高,排名 1。
  • Bob、Charlie 和 Judy 薪资相同,使用 RANK() 函数时,他们的排名依次为 2、2、2(跳过3、4),使用 DENSE_RANK() 函数时,排名连续为 2、2、2(名次连续)。
分组窗口函数查询

分组窗口函数NTILE() 将数据分为指定数量的组,每组的行数尽可能相等。假设我们要根据员工的薪资将他们分为四组(例如:高收入、中等收入、较低收入和最低收入),我们可以对每个部门使用 NTILE(4) 来实现:

    SELECTemployee_id,name,department_name,salary,NTILE(4) OVER (PARTITION BY department_name ORDER BY salary DESC) AS salary_quartile
FROM employees;

执行结果如下:
在这里插入图片描述
在这个结果中,每个部分都被分为了4组,以市场部为例:
市场部 的 David 和 Eve 位于最高收入组(1),Nancy在中等收入组(2),Frank分到了较低收入组(3),Karl 被分到了最低收入组(4)注意,由于 NTILE() 函数的目的是将数据分为尽可能相等的组,每个部门5个人,分为4组,每个部门肯定有两个人会分到同一个组内。

分布窗口函数查询
SELECTemployee_id,name,department_name,salary,PERCENT_RANK() OVER (PARTITION BY department_name ORDER BY salary DESC) AS salary_percent_rank,CUME_DIST() OVER (PARTITION BY department_name ORDER BY salary DESC) AS salary_cume_dist
FROM employees;

执行结果如下:
在这里插入图片描述
ERCENT_RANK() 说明:
PERCENT_RANK() 函数返回一个介于0到1之间的值,表示当前员工薪资在部门中的排名百分比。例如,如果一个员工的 salary_percent_rank 是0.50,这意味着他的薪资低于或等于部门中50%的员工薪资。

  • 在 “财务部” 中,Alice 的薪资是最高的,所以她的薪资百分比排名是0.00(即她是最高薪)。Judy、Bob 和 Charlie 的薪资相同,并且低于Alice,所以他们的薪资百分比排名是0.25。
  • Mike 的薪资是最低的,所以他的薪资百分比排名是1.00。

CUME_DIST() 说明:

  • CUME_DIST() 函数返回一个介于0到1之间的值,用于求分区中大于等于或小于等于当前行的数据在分区中的占比。如果是升序排列,则统计是:小于等于当前值的行数/总行数 ,如果是降序排列,则统计:大于等于当前值的行数/总行数。
  • 这里按薪水降序排列,表示大于或等于当前员工薪资的员工数量占部门总员工数量的比例。在 “财务部” 中,Alice 的 salary_cume_dist 是0.2,因为她的薪资是最高的。Judy、Bob 和 Charlie 的薪资相同,并且有4个的员工薪资大于等于他们,所以他们的 salary_cume_dist 是0.80。Mike 是最低薪资,部门所有人都大于等于他,所以他的 salary_cume_dist 是1。
取值窗口函数查询
LAG
SELECTemployee_id,name,department_name,salary,LAG(salary, 1, 0) OVER (PARTITION BY department_name ORDER BY salary DESC) AS prev_salary
FROM employees
ORDER BY department_name, salary DESC;

在这里插入图片描述
LAG(salary, 1, 0) 说明:

  • LAG() 函数返回当前员工之前第一个员工的薪资。如果没有前一个员工(即当前是部门中薪资最高的员工),则返回指定的默认值,这里我们使用0作为默认值。
LEAD
SELECTemployee_id,name,department_name,salary,LEAD(salary, 1, 0) OVER (PARTITION BY department_name ORDER BY salary DESC) AS next_salary
FROM employees
ORDER BY department_name, salary DESC;

在这里插入图片描述
LEAD(salary, 1, 0) 说明:

LEAD() 函数返回当前员工之后第一个员工的薪资。如果没有后一个员工(即当前是部门中薪资最低的员工),则返回指定的默认值,这里我们使用0作为默认值。

FIRST_VALUE

FIRST_VALUE函数用于取当前行所对应窗口的第一条数据的值。

SELECTemployee_id,name,department_name,salary,FIRST_VALUE(salary) OVER (PARTITION BY department_name ORDER BY salary DESC) AS max_salary_in_department
FROM employees
ORDER BY department_name, salary DESC;

执行结果如下:
在这里插入图片描述
FIRST_VALUE(salary) 说明:
FIRST_VALUE() 函数返回部门中薪资是按降序排列的,因此 FIRST_VALUE() 实际上是返回该部门的最高薪资。

LAST_VALUE

LAST_VALUE函数用于取当前行所对应窗口的最后一条数据的值。

SELECTemployee_id,name,department_name,salary,LAST_VALUE(salary) OVER (PARTITION BY department_name ORDER BY salary DESC) AS min_salary_in_department
FROM employees
ORDER BY department_name, salary DESC;

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

NTH_VALUE

获取窗口内第n行的值

SELECTemployee_id,name,department_name,salary,NTH_VALUE(salary, 3) OVER (PARTITION BY department_name ORDER BY salary DESC) AS third_salary_in_department
FROM employees
ORDER BY department_name, salary DESC;

执行结果

在这里插入图片描述

注意,这里third_salary_in_department会有null值,实际上这和分区内默认的取值范围有关,NTH_VALUE(salary, 3) 将始终返回每个分区中的第三行的值,因为这里默认取值范围从排序后的第一个行(即 ORDER BY 子句中的第一个值)到当前行,所以在当前行窗口范围内没有第三行的值时,就会显示null值。如果我们指定窗口取值范围如下:

SELECTemployee_id,name,department_name,salary,NTH_VALUE(salary, 3) OVER (PARTITION BY department_name ORDER BY salary descROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS third_salary_in_department
FROM employees
ORDER BY department_name;

则执行结果如下:
在这里插入图片描述
这里使用了ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING来指定窗口的范围为第一行到最后一行,这意味着窗口包括了分区内的所有行。在这种情况下,NTH_VALUE(salary, 3) 将始终返回每个分区中的第三行的值,只要该分区至少有三行数据。

4.窗口的范围

窗口的范围,有的资料上面也叫Framing(分帧),大致可以分为两种,一种是根据行(rows between)来划分,一种是根据列值(range between)来划分,它们都可以确定一个窗口应该包含哪些行。窗口的开始和结束可以使用以下关键字来定义:

  • UNBOUNDED PRECEDING:从分区中的第一行开始(前面所有行)。
  • CURRENT ROW:包括当前行。
  • n PRECEDING:从当前行之前的第 n 行开始。
  • n FOLLOWING:包括当前行之后第 n 行。
  • UNBOUNDED FOLLOWING:到分区中的最后一行结束(后面所有行)。
基于行划分:rows between…and…

语法格式如下:

sum(amount) over(order by <column> rows between <start> and <end>)

例如,假设你有一个包含销售数据的表,并希望计算每一行及其前两行的总和:

SELECTsales_date,sales_amount,SUM(sales_amount) OVER (ORDER BY sales_dateROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS running_total
FROMsales;
基于列值划分:range between…and…

语法格式如下:

sum(amount) over(order by <column> range between <start> and <end>)

例如,我们有一个包含商品数据的表,我们希望计算每个商品价格及其前后20单位价格范围内的商品总和:

SELECTproduct_id,product_price,SUM(product_price) OVER (ORDER BY product_priceRANGE BETWEEN 20 PRECEDING AND 20 FOLLOWING) AS price_range_total
FROMproducts;

假设当前行product_price值为70,那其窗口范围是product_price列的值位于50(70-20)到90(70+20)之间的所有的行。

5.窗口函数的缺省值

over后的窗口函数划分语句,包括partition byorder by(row|range)between ...and...这三部分,实际上这些内容也都可以省略不写。

  • partition by省略不写,表示不分区。在不进行分区的情况下,将会把整张表的全部内容作为窗口进行划分。
  • order by 省略,表示不排序
  • (row|range)between …and…省略不写,则使用其默认值,默认值分为以下两种情况:
    • over后面包含order by ,则默认值为:range between unbounded preceding and current row
    • over后面不包含order by ,则默认值为:rows between unbounded preceding and unbounded following。

小结:

可以看出,窗口产生的用法很类似聚合函数,不同的是聚合函数是将多行数据汇总为单个结果,窗口函数的话在同一个select中我们可以按照不同的列进行分区,而且多个窗口函数的列之间不受影响,功能很强大,语法也比较灵活,在进行复杂的数据分析或写一些报表时我们就可以考虑用窗口函数来去实现。

参考文档:

https://developer.aliyun.com/article/1541419
https://mysql.net.cn/doc/refman/8.0/en/window-functions.html
https://support.huaweicloud.com/intl/zh-cn/sqlref-spark-dli/dli_08_0069.html
https://www.cnblogs.com/xfeiyun/p/16965394.html

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

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

相关文章

SpringBoot(Ⅱ)——@SpringBootApplication注解+自动装配原理+约定大于配置

1. SpringBootApplication注解 SpringBootApplication标注在某个类上说明这个类是SpringBoot的主配置类&#xff0c;SpringBoot就通过运行这个类的main方法来启动SpringBoot应用&#xff1b; 并且Configuration注解中也有Component注解&#xff0c;所以这个主启动类/主配置类…

音视频入门知识(二)、图像篇

⭐二、图像篇 视频基本要素&#xff1a;宽、高、帧率、编码方式、码率、分辨率 ​ 其中码率的计算&#xff1a;码率(kbps)&#xff1d;文件大小(KB)&#xff0a;8&#xff0f;时间(秒)&#xff0c;即码率和视频文件大小成正比 YUV和RGB可相互转换 ★YUV&#xff08;原始数据&am…

CTFshow—爆破

Web21 直接访问页面的话会弹窗需要输入密码验证&#xff0c;抓个包看看&#xff0c;发现是Authorization认证&#xff0c;Authorization请求头用于验证是否有从服务器访问所需数据的权限。 把Authorization后面的数据进行base64解码&#xff0c;就是我们刚刚输入的账号密码。 …

lin.security提权靶场渗透

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

【魅力golang】之-泛型

早期的golang版本是不支持泛型的&#xff0c;这对于从其它语言转型做go开发的程序员来说&#xff0c;非常不友好&#xff0c;自 1.18开始golang正式支持泛型&#xff0c;解决了开发者在编写通用代码时的需求。泛型通过类型参数允许函数和数据结构支持多种类型&#xff0c;从而提…

数据结构(Java)——链表

1.概念及结构 链表是一种 物理存储结构上非连续 存储结构&#xff0c;数据元素的 逻辑顺序 是通过链表中的 引用链接 次序实现的 。 2.分类 链表的结构非常多样&#xff0c;以下情况组合起来就有 8 种链表结构&#xff1a; &#xff08;1&#xff09;单向或者双向 &#xff08;…

pdf有密码,如何实现pdf转换word?

PDF想要转换成其他格式&#xff0c;但是当我们将文件拖到PDF转换器进行转换的时候发现PDF文件带有密码怎么办&#xff1f;今天分享PDF有密码如何转换成word方法。 方法一、 PDF文件有两种密码&#xff0c;打开密码和限制编辑&#xff0c;如果是因为打开密码&#xff0c;建议使…

C++ 面向对象编程:继承中构造与析构函数顺序、继承中的同名属性访问、继承中的同名函数访问

在继承中&#xff0c;构造链中&#xff0c;先构造的后析构 见以下代码示例&#xff1a; #include<iostream> using namespace std;class animal1 { public:animal1() {cout << "animal1 构造" << endl;}~animal1() {cout << "animal1…

Springboot项目下面使用Vue3 + ElementPlus搭建侧边栏首页

Springboot项目下面、在html 页面 Vue3 ElementPlus 搭建侧边栏首页 1、效果图 2、static 文件下面的项目结构 3、代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>首页</title><…

Segment Routing Overview

大家觉得有意义和帮助记得及时关注和点赞!!! Segment Routing (SR) 是近年来网络领域的一项新技术&#xff0c;“segment” 在这里 指代网络隔离技术&#xff0c;例如 MPLS。如果快速回顾网络设计在过去几十年的 发展&#xff0c;我们会发现 SR 也许是正在形成的第三代网络设计…

USB 状态机及状态转换

文章目录 USB 状态机及状态转换连接状态供电状态默认状态地址状态配置状态挂起状态USB 状态机及状态转换 枚举完成之前,USB 设备要经过一系列的状态变化,才能最终完成枚举。这些状态是 连接状态 - attached供电状态 - powered默认状态 - default地址状态 - address配置状态 -…

如何在短时间内读懂复杂的英文文献?

当我们拿起一篇文献开始阅读时&#xff0c;就像是打开了一扇通往未知世界的大门。但别急着一头扎进去&#xff0c;咱们得像个侦探一样&#xff0c;带着疑问去探险。毕竟&#xff0c;知识的海洋深不可测&#xff0c;不带点“装备”怎么行&#xff1f;今天就聊聊&#xff0c;平时…

VS Code AI开发之Copilot配置和使用详解

随着AI开发工具的迅速发展&#xff0c;GitHub Copilot在Cursor、Winsuf、V0等一众工具的冲击下&#xff0c;推出了免费版本。接下来&#xff0c;我将为大家介绍GitHub Copilot的配置和使用方法。GitHub Copilot基于OpenAI Codex模型&#xff0c;旨在为软件开发者提供智能化的代…

FIR数字滤波器设计——窗函数设计法——滤波器的时域截断

与IIR数字滤波器的设计类似&#xff0c;设计FIR数字滤波器也需要事先给出理想滤波器频率响应 H ideal ( e j ω ) H_{\text{ideal}}(e^{j\omega}) Hideal​(ejω)&#xff0c;用实际的频率响应 H ( e j ω ) H(e^{j\omega}) H(ejω)去逼近 H ideal ( e j ω ) H_{\text{ideal}}…

No Python at ‘C:\Users\MI\AppData\Local\Programs\Python\Python39\python.exe‘

目录 一、检查环境配置 1.1 安装键盘“winR”键并输入cmd 1.2 输入“python” 二、解决问题 2.1 检查本地的python配置路径 2.2 打开PyCharm的Settings 2.3 找到Python Interpreter 2.4 删除当前python版本 2.5 新添版本 PyCharm运行时出现的错误&#xff1a; No Py…

重温设计模式--6、享元模式

文章目录 享元模式&#xff08;Flyweight Pattern&#xff09;概述享元模式的结构C 代码示例1应用场景C示例代码2 享元模式&#xff08;Flyweight Pattern&#xff09;概述 定义&#xff1a; 运用共享技术有效地支持大量细粒度的对象。 享元模式是一种结构型设计模式&#xff0…

多个微服务 Mybatis 过程中出现了Invalid bound statement (not found)的特殊问题

针对多个微服务的场景&#xff0c;记录一下这个特殊问题&#xff1a; 如果启动类上用了这个MapperScan注解 在resource 目录下必须建相同的 com.demo.biz.mapper 目录结构&#xff0c;否则会加载不到XML资源文件 。 并且切记是com/demo/biz 这样的格式创建&#xff0c;不要使用…

解读DeepseekV3

本年度还剩几天&#xff0c;Deepseek就发布了这么值得惊喜的产品&#xff0c;我觉得是真正做AI&#xff0c;也喜欢AI同学&#xff0c;对这个魔幻的2024年12月&#xff0c;一定是未来多少年想起都能回忆起这波澜壮阔的岁月。 我见过的最省的GPT4o&#xff0c;Claude&#xff0c…

一种寻路的应用

应用背景 利用长途车进行货物转运的寻路计算。例如从深圳到大连。可以走有很多条长途车的路线。需要根据需求计算出最合适路线。不同的路线的总里程数、总价、需要的时间不一样。客户根据需求进行选择。主要有一些细节&#xff1a; 全国的长途车车站的数据的更新&#xff1a; …

Linux-Ubuntu之串口通信

Linux-Ubuntu之串口通信 一&#xff0c;串口通信1.串口通信寄存器配置2.串口通信软件实现①手动波特率②自动波特率③主函数 二&#xff0c;printf和scanf实现串口的输入显示 一&#xff0c;串口通信 1.串口通信寄存器配置 串口通信利用接口是这个TTL&#xff0c;下载程序用的…