当前位置: 代码网 > it编程>数据库>Mysql > 一文深入解析Mysql的开窗函数(易懂版)

一文深入解析Mysql的开窗函数(易懂版)

2025年09月24日 Mysql 我要评论
前言sql 开窗函数(window function)是一种强大的分析工具,它能在保留原有数据行的基础上,对 "窗口"(指定范围的行集合)进行聚合、排名或分析计算,解决了传统gro

前言

sql 开窗函数(window function)是一种强大的分析工具,它能在保留原有数据行的基础上,对 "窗口"(指定范围的行集合)进行聚合、排名或分析计算,解决了传统group by聚合会合并行的局限性。

一、开窗函数的核心特点

  • 不合并行:与group by不同,开窗函数计算后会保留所有原始行,只是为每行附加一个计算结果。
  • 窗口定义:通过over()子句定义 "窗口"(即计算范围),可按条件分区、排序或限定行范围。
  • 适用场景:排名(如 top n)、累计计算(如累计求和)、移动分析(如近 3 天平均值)、前后行数据获取等。

二、基本语法结构

开窗函数的通用语法:

函数名(参数) over (
  [partition by 分区列1, 分区列2...]  -- 可选:按列分组,每组独立计算
  [order by 排序列1 [asc|desc], ...]  -- 可选:分区内的排序方式
  [rows | range 窗口范围]  -- 可选:定义窗口的具体行范围(行级窗口)
)
  • 函数名:可以是排名函数(rank()row_number()等)、聚合函数(sum()avg()等)或分析函数(lag()lead()等)。
  • over()子句:核心部分,用于定义 "窗口" 的规则。

三、over()子句详解

1. partition by:分区(分组)

  • 作用:将数据按指定列分成多个独立的 "分区",开窗函数在每个分区内单独计算(类似group by的分组,但不合并行)。
  • 示例:按 "部门" 分区,每个部门内部独立计算工资排名。

2. order by:分区内排序

  • 作用:指定分区内的行排序规则,影响排名函数的结果和窗口范围的界定。
  • 注意:若不指定partition by,则全表视为一个分区,按order by整体排序。

3. rows | range:窗口范围(行级窗口)

  • 作用:在分区内,进一步限定参与计算的行范围(如 "当前行 + 前 2 行 + 后 1 行")。
  • 关键字:
    • rows:基于物理行数界定范围(如 "前 2 行")。
    • range:基于值的逻辑范围界定(如 "值在当前行 ±10 以内的行"),仅支持数值 / 日期类型。
  • 常用范围表达式:
    • unbounded preceding:分区的第一行
    • current row:当前行
    • n preceding:当前行之前的第 n 行
    • n following:当前行之后的第 n 行
    • 组合示例:rows between 2 preceding and 1 following(当前行 + 前 2 行 + 后 1 行)

四、常用开窗函数分类及示例

以下示例基于员工表employee,结构如下:

idnamedepartmentsalaryhire_date
1张三技术部80002020-01-15
2李四技术部90002019-03-20
3王五技术部90002018-05-10
4赵六市场部70002021-02-05
5钱七市场部85002020-08-18

1. 排名函数(用于生成排名)

(1)row_number():生成唯一序号

  • 功能:为分区内的每行分配一个连续的唯一序号(即使值相同,序号也不同)。
  • 示例:按部门分区,按工资降序排名(工资相同则按入职时间升序):
select 
  name, 
  department, 
  salary,
  row_number() over (
    partition by department 
    order by salary desc, hire_date asc
  ) as row_num
from employee;
  • 结果:
    namedepartmentsalaryrow_num
    李四技术部90001(同工资,入职早排前)
    王五技术部90002
    张三技术部80003
    钱七市场部85001
    赵六市场部70002

(2)rank():带跳号的排名

  • 功能:相同值排名相同,后续排名会 "跳号"(如两个第 1 名,下一个是第 3 名)。
  • 示例:按部门分区,按工资降序排名:
select 
  name, 
  department, 
  salary,
  rank() over (
    partition by department 
    order by salary desc
  ) as rank_num
from employee;
  • 结果:
    namedepartmentsalaryrank_num
    李四技术部90001
    王五技术部90001(与李四并列第 1)
    张三技术部80003(跳号,直接第 3)
    钱七市场部85001
    赵六市场部70002

(3)dense_rank():无跳号的排名

  • 功能:相同值排名相同,后续排名不跳号(如两个第 1 名,下一个是第 2 名)。
  • 示例:按部门分区,按工资降序排名:
select 
  name, 
  department, 
  salary,
  dense_rank() over (
    partition by department 
    order by salary desc
  ) as dense_rank_num
from employee;
  • 结果:
    namedepartmentsalarydense_rank_num
    李四技术部90001
    王五技术部90001
    张三技术部80002(不跳号,第 2)
    钱七市场部85001
    赵六市场部70002

2. 聚合开窗函数(聚合函数 +over())

sum()avg()count()等聚合函数与over()结合,为每行计算所在窗口的聚合结果。

(1)全分区聚合(无order by和范围)

  • 功能:计算整个分区的聚合值(每行的结果相同)。
  • 示例:计算每个部门的平均工资,附加到每行:
select 
  name, 
  department, 
  salary,
  avg(salary) over (partition by department) as dept_avg_salary
from employee;
  • 结果:
    namedepartmentsalarydept_avg_salary
    张三技术部80008666.67((8000+9000+9000)/3)
    李四技术部90008666.67
    王五技术部90008666.67

(2)累计聚合(带order by和范围)

  • 功能:按排序顺序计算 "累计" 聚合值(如累计求和、累计平均值)。
  • 示例:按部门分区,按入职时间升序,计算累计工资总和:
select 
  name, 
  department, 
  hire_date,
  salary,
  sum(salary) over (
    partition by department 
    order by hire_date asc
    rows between unbounded preceding and current row  -- 从第一行到当前行
  ) as cumulative_salary
from employee;
  • 结果(技术部):
    namedepartmenthire_datesalarycumulative_salary
    王五技术部2018-05-1090009000(第一行,累计 = 自身)
    李四技术部2019-03-20900018000(累计 = 9000+9000)
    张三技术部2020-01-15800026000(累计 = 9000+9000+8000)

3. 分析函数(获取前后行数据)

(1)lag(列名, n):获取当前行的前 n 行数据

  • 功能:返回当前行之前第 n 行的指定列值(默认 n=1)。
  • 示例:获取每个部门中,当前员工的前一位入职员工的工资:
select 
  name, 
  department, 
  hire_date,
  salary,
  lag(salary, 1) over (
    partition by department 
    order by hire_date asc
  ) as prev_emp_salary
from employee;
  • 结果(技术部):
    namedepartmenthire_datesalaryprev_emp_salary
    王五技术部2018-05-109000null(第一行,无前一行)
    李四技术部2019-03-2090009000(前一行是王五的工资)
    张三技术部2020-01-1580009000(前一行是李四的工资)

(2)lead(列名, n):获取当前行的后 n 行数据

  • 功能:返回当前行之后第 n 行的指定列值(默认 n=1)。
  • 示例:获取每个部门中,当前员工的后一位入职员工的工资:
select 
  name, 
  department, 
  hire_date,
  salary,
  lead(salary, 1) over (
    partition by department 
    order by hire_date asc
  ) as next_emp_salary
from employee;
  • 结果(技术部):
    namedepartmenthire_datesalarynext_emp_salary
    王五技术部2018-05-1090009000(后一行是李四的工资)
    李四技术部2019-03-2090008000(后一行是张三的工资)
    张三技术部2020-01-158000null(最后一行,无后一行)

五、开窗函数与group by的区别

特性group by聚合开窗函数
行处理合并分组后的行(一行 / 组)保留所有原始行
计算范围整个分组可自定义窗口范围(分区、行范围)
结果列仅聚合结果 + 分组列原始列 + 开窗计算结果

六、注意事项

  • 排序影响order by在开窗函数中不仅影响排名,还会影响窗口范围的界定(如累计计算)。
  • 性能考量:复杂的窗口范围(如range)可能导致性能下降,大表建议优先用rows
  • 数据库支持:主流数据库(mysql 8.0+、postgresql、sql server、oracle)均支持开窗函数,但部分细节可能有差异。

通过上述讲解,可掌握开窗函数的核心语法和应用场景。实际使用时,需根据业务需求灵活组合partition byorder by和窗口范围,实现复杂的数据分析。

到此这篇关于mysql开窗函数的文章就介绍到这了,更多相关mysql开窗函数内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

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

发表评论

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