MySQL 一定要会的窗口函数

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

1.窗口函数的基本概念

窗口函数,也叫 OLAP 函数(Online Analytical Processing),可以对数据库数据进行实时分析处理。它们与普通的聚合函数不同,不会对结果进行分组,使得输出中的行数与输入中的行数相同。

2.窗口函数的语法

窗口函数的基本语法如下:

窗口函数([参数]) OVER (
    [PARTITION BY <分组列>] 
    [ORDER BY <排序列 ASC/DESC>] 
    [ROWS BETWEEN 开始行 AND 结束行]
)
  • PARTITION BY 子句用于指定分组列,类似于 GROUP BY
  • ORDER BY 子句用于指定排序列。
  • ROWS BETWEEN 子句用于指定窗口的范围。

3.常见的窗口函数

  1. 聚合窗口函数:如 SUM(), AVG(), COUNT(), MAX(), MIN() 等。
  2. 排序相关的函数:如 ROW_NUMBER(), RANK(), DENSE_RANK() 等。
  3. 前后行值访问:如 LAG(), LEAD() 等。

4.窗口函数示例

假设有一个销售数据表 sales,包含以下字段:id, product, category, sale_date, quantity, revenue。我们可以使用窗口函数来计算每个产品的累计销售数量:

SELECT 
    product, 
    sale_date, 
    quantity, 
    SUM(quantity) OVER (PARTITION BY product ORDER BY sale_date) AS cumulative_quantity
FROM 
    sales;

这个查询会根据 product 分组,并按 sale_date 排序,计算每个产品的累计销售数量。

5.补充排序和前后值

ROW_NUMBER()

ROW_NUMER() 为每一行分配一个唯一的序号,即使两行的值相同,它们也会被分配不同的序号。它的排序是连续的,没有间隔。

语法

ROW_NUMBER() OVER (PARTITION BY <分组列> ORDER BY <排序列>)

示例:根据每个员工的薪水降序排列,并分配一个唯一的行号。

SELECT 
    name, 
    salary, 
    ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num
FROM 
    employees;

RANK()

RANK() 为每一行分配一个序号,当两行的值相同时,它们会被分配相同的序号。排名会有跳跃,比如,如果有两个第二名,那么接下来的排名就是第四名。

语法:薪水相同的员工会有相同的排名,但排名会跳跃。

RANK() OVER (PARTITION BY <分组列> ORDER BY <排序列>)

示例

SELECT 
    name, 
    salary, 
    RANK() OVER (ORDER BY salary DESC) AS rank
FROM 
    employees;

DENSE_RANK()

DENSE_RANK()RAN() 类似,就是相同的行也会分配连续的序号,但是下一行的序号不会有间隔。比如,如果有两个第二名,那么接下来的排名就是第三名。

语法

DENSE_RANK() OVER (PARTITION BY <分组列> ORDER BY <排序列>)

示例:薪水相同的员工会有相同的排名,并且排名是连续的。

SELECT 
    name, 
    salary, 
    DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank
FROM 
    employees;

LAG()

LAG() 函数返回当前行之前第 N 行的值,如果 N 行不存在,则返回指定的默认值或 NULL

语法

LAG(column_name, offset, default_value) OVER (PARTITION BY <分组列> ORDER BY <排序列>)

示例:查看每个产品的销售额变化情况,即当前销售额与前一条记录的比较。

SELECT 
    product_id, 
    sale_date, 
    amount, -- offset 表示向前偏移的行数,这里是 1
    LAG(amount, 1, 0) OVER (PARTITION BY product_id ORDER BY sale_date) AS prev_amount
FROM 
    sales;

LEAD()

LEAD() 函数与 LAG() 相反,它返回当前行之后的第 N 行数据,如果后 N 行不存在,则返回默认值或 NULL

语法

LEAD(column_name, offset, default_value) OVER (PARTITION BY <分组列> ORDER BY <排序列>)

示例:查看每个产品的当前销售额和下一条记录的销售额。

SELECT 
    product_id, 
    sale_date, 
    amount, 
    LEAD(amount, 1, 0) OVER (PARTITION BY product_id ORDER BY sale_date) AS next_amount
FROM 
    sales;

6.实战练习

可以参考一些在线练习平台如 LeetCode 上的 SQL 题目来练习窗口函数的使用。

LeetCode SQL 178.分数排名

表: Scores

Column NameType
idint
scoredecimal

id 是该表的主键(有不同值的列)。
该表的每一行都包含了一场比赛的分数。Score 是一个有两位小数点的浮点值。

编写一个解决方案来查询分数的排名。排名按以下规则计算:

  • 分数应按从高到低排列。
  • 如果两个分数相等,那么两个分数的排名应该相同。
  • 在排名相同的分数后,排名数应该是下一个连续的整数。换句话说,排名之间不应该有空缺的数字。

score 降序返回结果表。

查询结果格式如下所示。

示例 1:

输入:
Scores 表:

idscore
13.50
23.65
34.00
43.85
54.00
63.65

输出:

scorerank
4.001
4.001
3.852
3.653
3.653
3.504

解题思路:根据分数排序的思路,我们可以使用 DENSE_RANK() 进行排序。

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

LeetCode SQL 180.连续出现的数字

表:Logs

Column NameType
idint
numvarchar

在 SQL 中,id 是该表的主键。
id 是一个自增列。

找出所有至少连续出现三次的数字。

返回的结果表中的数据可以按 任意顺序 排列。

结果格式如下面的例子所示:

示例 1:

输入:
Logs 表:

idnum
11
21
31
42
51
62
72

输出:
Result 表:

ConsecutiveNums
1

解释: 1 是唯一连续出现至少三次的数字。


解题思路:要判断是否是连续出现的数字,那么我们可以去判断它的前两位数字与当前数字是否相等,因此,我们这里可以使用 LAG() 函数去找出前两个数字,然后通过子查询去进行判断。

WITH numbered_logs AS (
    SELECT 
        num AS ConsecutiveNums,
        LAG(num, 1) OVER (ORDER BY id) AS prev_num,
        LAG(num, 2) OVER (ORDER BY id) AS prev_prev_num
    FROM logs
)
SELECT distinct ConsecutiveNums
FROM numbered_logs
WHERE prev_num = ConsecutiveNums AND prev_prev_num = prev_num;

LeetCode SQL 1789.员工的直属部门

表:Employee

Column NameType
employee_idint
department_idint
primary_flagvarchar

这张表的主键为 employee_id, department_id (具有唯一值的列的组合)
employee_id 是员工的ID
department_id 是部门的ID,表示员工与该部门有关系
primary_flag 是一个枚举类型,值分别为(‘Y’, ‘N’). 如果值为’Y’,表示该部门是员工的直属部门。 如果值是’N’,则否

一个员工可以属于多个部门。当一个员工加入超过一个部门的时候,他需要决定哪个部门是他的直属部门。请注意,当员工只加入一个部门的时候,那这个部门将默认为他的直属部门,虽然表记录的值为'N'.

请编写解决方案,查出员工所属的直属部门。

返回结果 没有顺序要求

返回结果格式如下例子所示:

示例 1:

输入:
Employee table:

employee_iddepartment_idprimary_flag
11N
21Y
22N
33N
42N
43Y
44N

输出:

employee_iddepartment_id
11
21
33
43

解释:

  • 员工 1 的直属部门是 1
  • 员工 2 的直属部门是 1
  • 员工 3 的直属部门是 3
  • 员工 4 的直属部门是 3

解题思路:我们发现,员工直属部门可能会有两种情况。情况一,就是 primary_flagY;情况二,就是部门数为 cnt1 的情况。除了可以用 UNION 将其合并之外,还能用窗口函数计算每个员工的部门数,然后通过子查询得到结果。

select
employee_id, department_id
from (
    select
    employee_id,
    department_id,
    primary_flag,
    count(*) over(partition by employee_id) cnt
    from Employee
) T
where cnt = 1 or primary_flag = 'Y';

LeetCode SQL 1204.最后一个能进入巴士的人

表: Queue

Column NameType
person_idint
person_namevarchar
weightint
turnint

person_id 是这个表具有唯一值的列。
该表展示了所有候车乘客的信息。
表中 person_id 和 turn 列将包含从 1 到 n 的所有数字,其中 n 是表中的行数。
turn 决定了候车乘客上巴士的顺序,其中 turn=1 表示第一个上巴士,turn=n 表示最后一个上巴士。
weight 表示候车乘客的体重,以千克为单位。

有一队乘客在等着上巴士。然而,巴士有1000 千克 的重量限制,所以其中一部分乘客可能无法上巴士。

编写解决方案找出 最后一个 上巴士且不超过重量限制的乘客,并报告 person_name 。题目测试用例确保顺位第一的人可以上巴士且不会超重。

返回结果格式如下所示。

示例 1:

输入:
Queue 表

person_idperson_nameweightturn
5Alice2501
4Bob1755
3Alex3502
6John Cena4003
1Winston5006
2Marie2004

输出:

person_name
John Cena

解释:
为了简化,Queue 表按 turn 列由小到大排序。

TurnIDNameWeightTotal Weight
15Alice250250
23Alex350600
36John Cena4001000(最后一个上)
42Marie2001200(无法上巴士)
54Bob175-
61Winston500-

解题思路:根据题目我们自然能想到要根据 turn 进行排序,然后累计计算 Weight,只要找到最接近 1000ID 就好了。而通过窗口函数,我们就可以实现累计计算,非常方便。

SELECT person_name
FROM
    (SELECT 
    *,sum(weight) OVER(ORDER BY turn) AS SUMWEIGHT
    FROM Queue) T
WHERE SUMWEIGHT <= 1000
ORDER BY SUMWEIGHT DESC
LIMIT 0,1;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值