赞
踩
MySQL 开窗函数(Window Functions)是 MySQL 8.0 版本引入的一个强大特性,它可以用于计算聚合的同时提供数据行的上下文信息。开窗函数可以分为以下几类:
窗口函数示例1:
mysql> SELECT time, subject, val, SUM(val) OVER (PARTITION BY subject ORDER BY time ROWS UNBOUNDED PRECEDING) AS running_total, AVG(val) OVER (PARTITION BY subject ORDER BY time ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS running_average FROM observations; +----------+---------+------+---------------+-----------------+ | time | subject | val | running_total | running_average | +----------+---------+------+---------------+-----------------+ | 07:00:00 | st113 | 10 | 10 | 9.5000 | | 07:15:00 | st113 | 9 | 19 | 14.6667 | | 07:30:00 | st113 | 25 | 44 | 18.0000 | | 07:45:00 | st113 | 20 | 64 | 22.5000 | | 07:00:00 | xh458 | 0 | 0 | 5.0000 | | 07:15:00 | xh458 | 10 | 10 | 5.0000 | | 07:30:00 | xh458 | 5 | 15 | 15.0000 | | 07:45:00 | xh458 | 30 | 45 | 20.0000 | | 08:00:00 | xh458 | 25 | 70 | 27.5000 | +----------+---------+------+---------------+-----------------+
窗口函数示例2:
mysql> SELECT time, subject, val, FIRST_VALUE(val) OVER w AS 'first', LAST_VALUE(val) OVER w AS 'last', NTH_VALUE(val, 2) OVER w AS 'second', NTH_VALUE(val, 4) OVER w AS 'fourth' FROM observations WINDOW w AS (PARTITION BY subject ORDER BY time ROWS UNBOUNDED PRECEDING); +----------+---------+------+-------+------+--------+--------+ | time | subject | val | first | last | second | fourth | +----------+---------+------+-------+------+--------+--------+ | 07:00:00 | st113 | 10 | 10 | 10 | NULL | NULL | | 07:15:00 | st113 | 9 | 10 | 9 | 9 | NULL | | 07:30:00 | st113 | 25 | 10 | 25 | 9 | NULL | | 07:45:00 | st113 | 20 | 10 | 20 | 9 | 20 | | 07:00:00 | xh458 | 0 | 0 | 0 | NULL | NULL | | 07:15:00 | xh458 | 10 | 0 | 10 | 10 | NULL | | 07:30:00 | xh458 | 5 | 0 | 5 | 10 | NULL | | 07:45:00 | xh458 | 30 | 0 | 30 | 10 | 30 | | 08:00:00 | xh458 | 25 | 0 | 25 | 10 | 30 | +----------+---------+------+-------+------+--------+--------+
窗口的单位(frame unit):
窗口的范围:
frame_between:
BETWEEN frame_start AND frame_end
frame_start, frame_end: {
CURRENT ROW
| UNBOUNDED PRECEDING
| UNBOUNDED FOLLOWING
| expr PRECEDING
| expr FOLLOWING
}
窗口参数示例:
10 PRECEDING
INTERVAL 5 DAY PRECEDING
5 FOLLOWING
INTERVAL '2:30' MINUTE_SECOND FOLLOWING
注: 如果使用的是RANGE,则需要根据窗口排序中的列,选择对应的时间单位
常用的时间单位:MICROSECOND (microseconds), SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, or YEAR…
mysql> SELECT DATE_ADD('2018-05-01',INTERVAL 1 DAY); -> '2018-05-02' mysql> SELECT DATE_SUB('2018-05-01',INTERVAL 1 YEAR); -> '2017-05-01' mysql> SELECT DATE_ADD('2020-12-31 23:59:59', -> INTERVAL 1 SECOND); -> '2021-01-01 00:00:00' mysql> SELECT DATE_ADD('2018-12-31 23:59:59', -> INTERVAL 1 DAY); -> '2019-01-01 23:59:59' mysql> SELECT DATE_ADD('2100-12-31 23:59:59', -> INTERVAL '1:1' MINUTE_SECOND); -> '2101-01-01 00:01:00' mysql> SELECT DATE_SUB('2025-01-01 00:00:00', -> INTERVAL '1 1:1:1' DAY_SECOND); -> '2024-12-30 22:58:59' mysql> SELECT DATE_ADD('1900-01-01 00:00:00', -> INTERVAL '-1 10' DAY_HOUR); -> '1899-12-30 14:00:00' mysql> SELECT DATE_SUB('1998-01-02', INTERVAL 31 DAY); -> '1997-12-02' mysql> SELECT DATE_ADD('1992-12-31 23:59:59.000002', -> INTERVAL '1.999999' SECOND_MICROSECOND); -> '1993-01-01 00:00:01.000001'
PERCENT_RANK()函数返回一个从0到1的数字。
对于指定的行,PERCENT_RANK()计算行的等级减1,除以评估的分区或查询结果集中的行数减1: (rank - 1) / (total_rows - 1) 在此公式中,rank是指定行的等级,total_rows是要计算的行数。
表: Students
+---------------+------+
| Column Name | Type |
+---------------+------+
| student_id | int |
| department_id | int |
| mark | int |
+---------------+------+
student_id 包含唯一值。
该表的每一行都表示一个学生的 ID,该学生就读的院系 ID,以及他们的考试分数。
编写一个解决方案,以百分比的形式报告每个学生在其部门的排名,其中排名的百分比使用以下公式计算:
(student_rank_in_the_department - 1) * 100 / (the_number_of_students_in_the_department - 1)。 percentage 应该 四舍五入到小数点后两位。
student_rank_in_the_department 由 mark 的降序决定,mark 最高的学生是 rank 1。如果两个学生得到相同的分数,他们也会得到相同的排名。
以 任意顺序 返回结果表。
结果格式如下所示。
示例 1:
输入:
Students 表:
+------------+---------------+------+
| student_id | department_id | mark |
+------------+---------------+------+
| 2 | 2 | 650 |
| 8 | 2 | 650 |
| 7 | 1 | 920 |
| 1 | 1 | 610 |
| 3 | 1 | 530 |
+------------+---------------+------+
输出:
+------------+---------------+------------+
| student_id | department_id | percentage |
+------------+---------------+------------+
| 7 | 1 | 0.0 |
| 1 | 1 | 50.0 |
| 3 | 1 | 100.0 |
| 2 | 2 | 0.0 |
| 8 | 2 | 0.0 |
+------------+---------------+------------+
解释:
对于院系 1:
答案:
select student_id
,department_id
,round((percent_rank() over (partition by department_id order by mark desc))*100,2) as percentage
from Students
FIRST_VALUE() 函数的作用是返回子集中第一行的指定列数据,该函数的语法如下:
FIRST_VALUE(expr)
OVER (
[partition_definition] [order_definition] [frame_clause]
)
其中,expr 为要获取数据的列明或者表达式,partition_definition 和 partition_definition 与 ROW_NUMBER() 函数一致;
frame_clause 的语法如下:
frame_unit {<frame_start>|<frame_between>}
LAST_VALUE() 和 FIRST_VALUE() 十分类似,区别在于 LAST_VALUE() 返回的是子集中的最后一条数据的指定列数据
NTH_VALUE() 的作用是获取指定 frame 中的第
N
个记录行的指定数据,对应的函数语法如下所示:
NTH_VALUE(expr, N)
OVER (
[partition_definition] [order_definition] [frame_clause]
)
表: CoffeeShop
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| drink | varchar |
+-------------+---------+
id 是该表的主键(具有唯一值的列)。
该表中的每一行都显示了订单 id 和所点饮料的名称。一些饮料行为 null。
编写一个解决方案将 drink 的 null 值替换为前面最近一行不为 null 的 drink。保证表第一行的 drink 不为 null。
返回 与输入顺序相同的 结果表。
查询结果格式示例如下。
示例 1:
输入:
CoffeeShop 表:
+----+-------------------+
| id | drink |
+----+-------------------+
| 9 | Rum and Coke |
| 6 | null |
| 7 | null |
| 3 | St Germain Spritz |
| 1 | Orange Margarita |
| 2 | null |
+----+-------------------+
输出:
+----+-------------------+
| id | drink |
+----+-------------------+
| 9 | Rum and Coke |
| 6 | Rum and Coke |
| 7 | Rum and Coke |
| 3 | St Germain Spritz |
| 1 | Orange Margarita |
| 2 | Orange Margarita |
+----+-------------------+
解释:
对于 ID 6,之前不为空的值来自 ID 9。我们将 null 替换为 “Rum and Coke”。
对于 ID 7,之前不为空的值来自 ID 9。我们将 null 替换为 “Rum and Coke”。
对于 ID 2,之前不为空的值来自 ID 1。我们将 null 替换为 “Orange Margarita”。
请注意,输出中的行与输入中的行相同。
答案:
select id
,first_value(drink) over(partition by group_id order by row_id) as drink
from
(select *
,sum(IF(drink is null, 0, 1)) over(order by row_id) as group_id
from
(select *
,row_number() over() as row_id
from coffeeshop) t0
) t1
;
表:Servers
+----------------+----------+
| Column Name | Type |
+----------------+----------+
| server_id | int |
| status_time | datetime |
| session_status | enum |
+----------------+----------+
(server_id, status_time, session_status) 是这张表的主键(有不同值的列的组合)。
session_status 是 (‘start’, ‘stop’) 的 ENUM (category)。
这张表的每一行包含 server_id, status_time 和 session_status。
编写一个解决方案来查找服务器 运行 的 总时间。输出应四舍五入为最接近的 整天数。
以 任意 顺序返回结果表。
结果格式如下所示。
示例:
输入:
Servers 表:
+-----------+---------------------+----------------+ | server_id | status_time | session_status | +-----------+---------------------+----------------+ | 3 | 2023-11-04 16:29:47 | start | | 3 | 2023-11-05 01:49:47 | stop | | 3 | 2023-11-25 01:37:08 | start | | 3 | 2023-11-25 03:50:08 | stop | | 1 | 2023-11-13 03:05:31 | start | | 1 | 2023-11-13 11:10:31 | stop | | 4 | 2023-11-29 15:11:17 | start | | 4 | 2023-11-29 15:42:17 | stop | | 4 | 2023-11-20 00:31:44 | start | | 4 | 2023-11-20 07:03:44 | stop | | 1 | 2023-11-20 00:27:11 | start | | 1 | 2023-11-20 01:41:11 | stop | | 3 | 2023-11-04 23:16:48 | start | | 3 | 2023-11-05 01:15:48 | stop | | 4 | 2023-11-30 15:09:18 | start | | 4 | 2023-11-30 20:48:18 | stop | | 4 | 2023-11-25 21:09:06 | start | | 4 | 2023-11-26 04:58:06 | stop | | 5 | 2023-11-16 19:42:22 | start | | 5 | 2023-11-16 21:08:22 | stop | +-----------+---------------------+----------------+
输出:
+-------------------+
| total_uptime_days |
+-------------------+
| 1 |
+-------------------+
解释:
对于 server ID 3:
从 2023-11-04 16:29:47 到 2023-11-05 01:49:47: ~9.3 小时
从 2023-11-25 01:37:08 到 2023-11-25 03:50:08: ~2.2 小时
从 2023-11-04 23:16:48 到 2023-11-05 01:15:48: ~1.98 小时
server 3 共计:~13.48 小时
对于 server ID 1:
从 2023-11-13 03:05:31 到 2023-11-13 11:10:31: ~8 小时
从 2023-11-20 00:27:11 到 2023-11-20 01:41:11: ~1.23 小时
server 1 共计:~9.23 小时
对于 server ID 4:
从 2023-11-29 15:11:17 到 2023-11-29 15:42:17: ~0.52 小时
从 2023-11-20 00:31:44 到 2023-11-20 07:03:44: ~6.53 小时
从 2023-11-30 15:09:18 到 2023-11-30 20:48:18: ~5.65 小时
从 2023-11-25 21:09:06 到 2023-11-26 04:58:06: ~7.82 小时
server 4 共计:~20.52 小时
对于 server ID 5:
从 2023-11-16 19:42:22 到 2023-11-16 21:08:22: ~1.43 小时
server 5 共计:~1.43 小时
所有服务器的累积运行时间总计约为 44.46 小时,相当于一整天加上一些额外的小时。然而,由于我们只考虑整天,因此最终输出四舍五入为 1 整天。
答案:
select floor(sum(diff)/24) as total_uptime_days
from
(
select *
,timestampdiff(second, status_time, next_time)/3600 as diff
from
(
select *
,lead(status_time, 1, null) over(partition by server_id order by status_time) as next_time
from Servers
) t0
where next_time is not null and session_status = 'start'
) t1
表: Transactions
+------------------+----------+
| Column Name | Type |
+------------------+----------+
| user_id | int |
| spend | decimal |
| transaction_date | datetime |
+------------------+----------+
(user_id, transaction_date) 是这张表具有唯一值的列。
该表包含 user_id, spend,和 transaction_date。
编写一个查询,找到符合要求的用户的 第三笔交易 (如果他们有至少三笔交易),并且满足 前两笔交易 的花费 低于 第三笔交易的花费。
返回 按 升序 user_id 排序的结果表。
结果格式如下例所示。
示例 1:
输入:
Transactions table:
+---------+--------+---------------------+
| user_id | spend | transaction_date |
+---------+--------+---------------------+
| 1 | 65.56 | 2023-11-18 13:49:42 |
| 1 | 96.0 | 2023-11-30 02:47:26 |
| 1 | 7.44 | 2023-11-02 12:15:23 |
| 1 | 49.78 | 2023-11-12 00:13:46 |
| 2 | 40.89 | 2023-11-21 04:39:15 |
| 2 | 100.44 | 2023-11-20 07:39:34 |
| 3 | 37.33 | 2023-11-03 06:22:02 |
| 3 | 13.89 | 2023-11-11 16:00:14 |
| 3 | 7.0 | 2023-11-29 22:32:36 |
+---------+--------+---------------------+
输出
+---------+-------------------------+------------------------+
| user_id | third_transaction_spend | third_transaction_date |
+---------+-------------------------+------------------------+
| 1 | 65.56 | 2023-11-18 13:49:42 |
+---------+-------------------------+------------------------+
解释
答案:
select user_id
,spend as third_transaction_spend
,transaction_date as third_transaction_date
from
(select *
,lag(spend, 1, null) over(partition by user_id order by transaction_date) as spend1
,lag(spend, 2, null) over(partition by user_id order by transaction_date) as spend2
,row_number() over(partition by user_id order by transaction_date) as rn
from transactions ) t0
where rn = 3
AND spend1 < spend AND spend2 < spend;
表:Employee
+-------------+------+
| Column Name | Type |
+-------------+------+
| id | int |
| month | int |
| salary | int |
+-------------+------+
(id, month) 是该表的主键(具有唯一值的列的组合)。
表中的每一行表示 2020 年期间员工一个月的工资。
编写一个解决方案,在一个统一的表中计算出每个员工的 累计工资汇总 。
员工的 累计工资汇总 可以计算如下:
对于该员工工作的每个月,将 该月 和 前两个月 的工资 加 起来。这是他们当月的 3 个月总工资和 。如果员工在前几个月没有为公司工作,那么他们在前几个月的有效工资为 0 。
不要 在摘要中包括员工 最近一个月 的 3 个月总工资和。
不要 包括雇员 没有工作 的任何一个月的 3 个月总工资和。
返回按 id 升序排序 的结果表。如果 id 相等,请按 month 降序排序。
结果格式如下所示。
示例 1
输入:
Employee table:
+----+-------+--------+
| id | month | salary |
+----+-------+--------+
| 1 | 1 | 20 |
| 2 | 1 | 20 |
| 1 | 2 | 30 |
| 2 | 2 | 30 |
| 3 | 2 | 40 |
| 1 | 3 | 40 |
| 3 | 3 | 60 |
| 1 | 4 | 60 |
| 3 | 4 | 70 |
| 1 | 7 | 90 |
| 1 | 8 | 90 |
+----+-------+--------+
输出:
+----+-------+--------+
| id | month | Salary |
+----+-------+--------+
| 1 | 7 | 90 |
| 1 | 4 | 130 |
| 1 | 3 | 90 |
| 1 | 2 | 50 |
| 1 | 1 | 20 |
| 2 | 1 | 20 |
| 3 | 3 | 100 |
| 3 | 2 | 40 |
+----+-------+--------+
解释:
员工 “1” 有 5 条工资记录,不包括最近一个月的 “8”:
+----+-------+--------+
| id | month | salary |
+----+-------+--------+
| 1 | 7 | 90 | (90 + 0 + 0)
| 1 | 4 | 130 | (60 + 40 + 30)
| 1 | 3 | 90 | (40 + 30 + 20)
| 1 | 2 | 50 | (30 + 20 + 0)
| 1 | 1 | 20 | (20 + 0 + 0)
+----+-------+--------+
请注意,‘7’ 月的 3 个月的总和是 90,因为他们没有在 ‘6’ 月或 ‘5’ 月工作。
员工 ‘2’ 只有一个工资记录(‘1’ 月),不包括最近的 ‘2’ 月。
+----+-------+--------+
| id | month | salary |
+----+-------+--------+
| 2 | 1 | 20 | (20 + 0 + 0)
+----+-------+--------+
员工 ‘3’ 有两个工资记录,不包括最近一个月的 ‘4’ 月:
+----+-------+--------+
| id | month | salary |
+----+-------+--------+
| 3 | 3 | 100 | (60 + 40 + 0)
| 3 | 2 | 40 | (40 + 0 + 0)
+----+-------+--------+
答案:
select id
,month
,sum(salary) over(partition by id order by month range between 2 preceding and current row) as salary
-- 窗口为最近三个月,是对值进行偏移,所以这里是range,而不是用rows,rows是对行进行偏移
-- 另外这里月是整数,如果是日期月份比如(2022-06),则需要改成Interval 2 month
from employee
where (id, month) not in ( -- 过滤掉最近一个月
select id, max(month) as month
from employee
group by id
)
order by id, month desc;
表: Steps
+-------------+------+
| Column Name | Type |
+-------------+------+
| user_id | int |
| steps_count | int |
| steps_date | date |
+-------------+------+
(user_id, steps_date) 是此表的主键。
该表的每一行包含 user_id、steps_count 和 steps_date。
编写一个解决方案,计算出每个用户的 3-day 滚动平均步数 。
计算 n-day 滚动平均值 的计算方式如下:
对于每一天,如果有可用数据的情况下,我们会计算以该天为结束的 n 天连续步数的平均值,否则,对于该天来说,n 天滚动平均步数是未定义的。
输出 user_id 、 steps_date 和滚动平均值。并将滚动平均值四舍五入到 两位小数。
返回结果表以user_id 和 steps_date 升序 排序。
结果的格式如下示例。
示例 1:
输入:
Steps table:
+---------+-------------+------------+ | user_id | steps_count | steps_date | +---------+-------------+------------+ | 1 | 687 | 2021-09-02 | | 1 | 395 | 2021-09-04 | | 1 | 499 | 2021-09-05 | | 1 | 712 | 2021-09-06 | | 1 | 576 | 2021-09-07 | | 2 | 153 | 2021-09-06 | | 2 | 171 | 2021-09-07 | | 2 | 530 | 2021-09-08 | | 3 | 945 | 2021-09-04 | | 3 | 120 | 2021-09-07 | | 3 | 557 | 2021-09-08 | | 3 | 840 | 2021-09-09 | | 3 | 627 | 2021-09-10 | | 5 | 382 | 2021-09-05 | | 6 | 480 | 2021-09-01 | | 6 | 191 | 2021-09-02 | | 6 | 303 | 2021-09-05 | +---------+-------------+------------+
输出:
+---------+------------+-----------------+
| user_id | steps_date | rolling_average |
+---------+------------+-----------------+
| 1 | 2021-09-06 | 535.33 |
| 1 | 2021-09-07 | 595.67 |
| 2 | 2021-09-08 | 284.67 |
| 3 | 2021-09-09 | 505.67 |
| 3 | 2021-09-10 | 674.67 |
+---------+------------+-----------------+
解释:
select user_id
,steps_date
,round(rolling_average, 2) as rolling_average
from
(select *
,avg(steps_count) over(partition by user_id order by steps_date range between interval 2 day preceding and current row) as rolling_average -- 近三天的平均步数
,count(1) over(partition by user_id order by steps_date range between interval 2 day preceding and current row) as cnt
-- 近三天的天数,用于判断数据行是否真的有3天
from steps) t1
where cnt = 3
order by user_id
,steps_date
;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。