假设有一个名为Employee的表,它有以下的结构:
CREATE TABLE Employee (
ID INT PRIMARY KEY,
Name VARCHAR(50),
Department VARCHAR(50),
Salary DECIMAL(10, 2)
);
这个表有四个字段:ID(员工的唯一标识符),Name(员工的名字),Department(员工所在的部门),Salary(员工的工资)。
现在,让我们使用这个表来学习窗口函数的基本用法,窗口函数是mysql进阶必须掌握的。
1. 什么是窗口函数?
窗口函数是MySQL 8.0新增的一个重要功能,可以为数据分析提供强大的支持,例如计算分组排名、累积求和、同比/环比增长率等。
2. 窗口函数的基本语法
窗口函数的定义如下:
window_function ( expr ) OVER ( PARTITION BY ... ORDER BY ... frame_clause )
其中,window_function
是窗口函数的名称;expr
是参数,有些函数不需要参数;OVER
子句包含三个选项:分区(PARTITION BY
)、排序(ORDER BY
)以及窗口大小(frame_clause
)。
例如,以下的查询返回每个部门的平均工资:
SELECT Department, AVG(Salary) OVER (PARTITION BY Department) as AvgSalary
FROM Employee;
3. 分区选项(PARTITION BY)
PARTITION BY
选项用于将数据行拆分成多个分区(组),窗口函数基于每一行数据所在的组进行计算并返回结果,它的作用类似于GROUP BY
分组。
例如,以下的查询返回每个部门每个员工的工资以及他们所在部门的平均工资:
SELECT Department, Salary, AVG(Salary) OVER (PARTITION BY Department) as AvgSalary
FROM Employee;
4. 排序选项(ORDER BY)
OVER
子句中的ORDER BY
选项用于指定分区内的排序方式,与ORDER BY
子句的作用类似,通常用于数据的排名分析。
例如,以下的查询返回每个部门每个员工的工资以及他们在部门内的工资排名:
SELECT Department, Salary, RANK() OVER (PARTITION BY Department ORDER BY Salary DESC) as Rank
FROM Employee;
5. 窗口选项(frame_clause)
frame_clause
选项用于在当前分区内指定一个计算窗口,也就是一个与当前行相关的数据子集。指定了窗口之后,分析函数不再基于分区进行计算,而是基于窗口内的数据进行计算。
例如,以下的查询返回每个部门每个员工的工资以及他们在部门内的累积工资(从部门工资最低的员工开始,一直累加到当前员工的工资):
SELECT Department, Salary, SUM(Salary) OVER (PARTITION BY Department ORDER BY Salary ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as CumulativeSalary
FROM Employee;
解释一下这段语句:
frame_clause
是窗口函数的一个选项,用于在当前分区内指定一个计算窗口,也就是一个与当前行相关的数据子集。在这个查询中,frame_clause
是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
定义了一个窗口,这个窗口从分区的第一行开始,到当前行结束。UNBOUNDED PRECEDING
表示窗口的开始没有限制,始终从分区的第一行开始;CURRENT ROW
表示窗口的结束是当前行。
然后,ORDER BY Salary
是用来指定分区内的排序方式的。在这个查询中,我们按照工资的大小对每个部门的员工进行排序。这样,当我们计算累积工资时,就可以确保我们是按照工资的顺序进行累加的。
frame_clause
可以有以下几种形式,这里做一些简单的举例:
ROWS UNBOUNDED PRECEDING
:窗口从分区的第一行开始,到当前行结束。
ROWS N PRECEDING
:窗口从当前行的前N行开始,到当前行结束。
ROWS CURRENT ROW
:窗口只包含当前行。
ROWS N FOLLOWING
:窗口从当前行开始,到当前行的后N行结束。
ROWS UNBOUNDED FOLLOWING
:窗口从当前行开始,到分区的最后一行结束。
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
:窗口从分区的第一行开始,到当前行结束。
ROWS BETWEEN N PRECEDING AND M FOLLOWING
:窗口从当前行的前N行开始,到当前行的后M行结束。
6. 常用的窗口函数
RANK()
: 计算分区内的排名。
DENSE_RANK()
: 计算分区内的排名,但在计算排名时会忽略并列的情况,即相同的值是同一个排名。
ROW_NUMBER()
: 返回分区内的行号,即使值相同,行号也会递增。
例如,以下的查询返回每个部门每个员工的工资以及他们在部门内的工资排名(使用RANK()和DENSE_RANK()):
SELECT Department, Salary, RANK() OVER (PARTITION BY Department ORDER BY Salary DESC) as Rank, DENSE_RANK() OVER (PARTITION BY Department ORDER BY Salary DESC) as DenseRank
FROM Employee;
MySQL还支持以下的窗口函数:
LEAD(): 返回按照某种排序顺序的下一行的值。
LAG(): 返回按照某种排序顺序的上一行的值。
FIRST_VALUE(): 返回按照某种排序顺序的第一行的值。
LAST_VALUE(): 返回按照某种排序顺序的最后一行的值。
NTH_VALUE(): 返回按照某种排序顺序的第N行的值。
NTILE(): 将排序后的行分成N个组,并返回每一行所在的组。
PERCENT_RANK(): 返回每一行的百分比排名。
CUME_DIST(): 返回小于等于当前行的值的比例。
这些函数都可以用在OVER子句中,和RANK(), DENSE_RANK(), ROW_NUMBER()一样,可以用来进行复杂的数据分析。更多指南详见myqsl窗口函数官方手册。
下面记录一些sql算法题中使用到窗口函数时楼主的通过代码,不定时更新。
# 使用窗口函数 给Employee表增加一个字段 即该员工的工资在其部门的排名 因为两个相同的工资算同一名 所以要选择DENSE_RANK()
select t2.name Department, t1.name Employee ,t1.salary from
(select * , DENSE_RANK() OVER (PARTITION BY departmentId ORDER BY salary desc) ranking # 这里的列名别名不能写成rank 因为其是mysql的保留关键字
from Employee ) t1
left join Department t2
on t1.departmentId = t2.id
where t1.ranking <= 3