新特性解读 | 窗口函数的适用场景
作者:赵佳慧
爱可生售后团队成员,主要负责公司运维平台故障诊断。喜爱技术,努力在 IT 行业中磨练自己。
本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
一、窗口函数
“窗口”可理解为记录集合。“窗口函数”可理解为在满足某种条件的记录集合上执行的特殊函数,对于每条记录都要在此窗口内执行。
二、快速理解窗口函数
通过举例,快速理解窗口函数。
2.1 举例
1)创建表 user,表 user 的数据如下:
mysql> select * from user;
+‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| id | name | address | createtime |
+‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 1 | aa | cun | 2020‐06‐01 00:00:00 |
| 2 | bb | cun | 2020‐06‐01 00:00:00 |
| 3 | bb | shi | 2020‐06‐01 01:00:00 |
| 4 | bb | shi | 2020‐06‐01 01:00:00 |
| 5 | cc | cun | 2020‐06‐01 01:00:00 |
| 6 | tt | cun | 2020‐06‐03 01:00:00 |
| 7 | eee | cun | 2020‐06‐04 01:00:00 |
| 8 | eee | cun | 2020‐06‐04 01:00:00 |
| 9 | xx | shen | 2020‐06‐02 01:00:00 |
+‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
9 rows in set (0.00 sec)
2)窗口函数的使用
SELECT createtime,
row_number() over(order by createtime) AS cr
FROM user ;
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐+
| createtime | ll |
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐+
| 2020‐06‐01 00:00:00 | 1 |
| 2020‐06‐01 00:00:00 | 2 |
| 2020‐06‐01 01:00:00 | 3 |
| 2020‐06‐01 01:00:00 | 4 |
| 2020‐06‐01 01:00:00 | 5 |
| 2020‐06‐02 01:00:00 | 6 |
| 2020‐06‐03 01:00:00 | 7 |
| 2020‐06‐04 01:00:00 | 8 |
| 2020‐06‐04 01:00:00 | 9 |
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐+
9 rows in set (0.00 sec)
row_number() over(order by createtime) as cr 这部分为窗口函数。over(order by createtime)为窗口规范,函数 row_number() 即对窗口的数据进行编号。所以上述 sql 的意思为:先对 createtime 进行排序,然后对每行数据进行编号。
三、窗口函数的适用场景
下面举例说明在哪些场景下适用窗口函数。
3.1 数据准备
1)创建用户表 user 并插入数据。
mysql> select * from user;
+‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| id | name | address | createtime |
+‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 1 | 李四 | 广州 | 2020‐06‐01 00:00:00 |
| 2 | 老三 | 广西 | 2020‐06‐01 00:00:00 |
| 3 | 张三 | 广州 | 2020‐06‐01 01:00:00 |
| 4 | 王五 | 上海 | 2020‐06‐01 01:00:00 |
| 5 | 吴迪 | 深圳 | 2020‐06‐01 01:00:00 |
| 6 | 赵六 | 北京 | 2020‐06‐03 01:00:00 |
| 7 | 刘六 | 北京 | 2020‐06‐04 01:00:00 |
| 8 | 刘开 | 江西 | 2020‐06‐04 01:00:00 |
| 9 | 张劳 | 上海 | 2020‐06‐02 01:00:00 |
+‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
9 rows in set (0.00 sec)
2)创建交易表 transaction 并插入数据。
mysql> select * from transaction;
+‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+
| id | userid | amount | paydate |
+‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+
| 1 | 3 | 23 | 2020‐07‐01 |
| 2 | 1 | 34 | 2020‐07‐01 |
| 3 | 3 | 56 | 2020‐07‐01 |
| 4 | 3 | 32 | 2020‐07‐01 |
| 5 | 2 | 100 | 2020‐07‐03 |
| 6 | 3 | 100 | 2020‐07‐03 |
| 7 | 2 | 198 | 2020‐07‐01 |
| 8 | 4 | 209 | 2020‐07‐01 |
| 9 | 4 | 209 | 2020‐07‐03 |
| 10 | 4 | 209 | 2020‐07‐10 |
| 11 | 3 | 239 | 2020‐07‐14 |
| 12 | 6 | 100 | 2020‐07‐14 |
| 13 | 3 | 100 | 2020‐07‐30 |
| 14 | 3 | 140 | 2020‐07‐30 |
| 15 | 3 | 148 | 2020‐07‐30 |
| 16 | 3 | 89 | 2020‐07‐30 |
+‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+
16 rows in set (0.00 sec)
3.2 场景一
在拥有用户表和交易表的前提下,可以计算出每个用户的交易量占总交易量的百分比。由此我们可以快速得出每个用户的交易占比。
sql 如下:
SELECT a.name,
a.everymoney,
sum(a.everymoney) over() AS totalmoney,
a.everymoney/(sum(a.everymoney) over()) AS percent
FROM
(SELECT u.name,
sum(t.amount) everymoney
FROM user u
JOIN transaction t
ON u.id=t.userid
GROUP BY u.name) a;
+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+
| name | everymoney | totalmoney | percent |
+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+
| 张三 | 1694 | 6359 | 0.2664 |
| 李四 | 2014 | 6359 | 0.3167 |
| 老三 | 1491 | 6359 | 0.2345 |
| 王五 | 1060 | 6359 | 0.1667 |
| 赵六 | 100 | 6359 | 0.0157 |
+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+
5 rows in set (0.00 sec)
通过子查询 a 对用户进行分组计算出每个用户的总交易金额(everymoney),再通过子查询 aa 使用 sum()+over 子句计算出所有用户总的交易金额(totalmoney),最后将每个用户总交易金额比上所有用户总交易金额(a.everymoney/(sum(a.everymoney) over())。
3.3 场景二
在拥有用户表和交易表的前提下,可以计算出每天交易金额位于第一的用户。
sql 如下:
SELECT a.name,
a.paydate,
row_number()
OVER w AS num
FROM
(SELECT u.name,
paydate,
sum(amount) AS total
FROM user u
JOIN transaction t
ON u.id=t.userid
GROUP BY u.name,paydate) a window w AS (partition by paydate
ORDER BY a.total desc);
+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐+
| name | paydate | num |
+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐+
| 王五 | 2020‐07‐01 | 1 |
| 老三 | 2020‐07‐01 | 2 |
| 张三 | 2020‐07‐01 | 3 |
| 李四 | 2020‐07‐01 | 4 |
| 王五 | 2020‐07‐03 | 1 |
| 张三 | 2020‐07‐03 | 2 |
| 老三 | 2020‐07‐03 | 3 |
| 王五 | 2020‐07‐10 | 1 |
| 张三 | 2020‐07‐14 | 1 |
| 赵六 | 2020‐07‐14 | 2 |
| 张三 | 2020‐07‐30 | 1 |
+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐+
11 rows in set (0.00 sec)
上述 sql 通过子查询 a 对用户和 paydate 进行分组,计算出每个用户每天的交易金额
(total),再通过窗口函数 row_number() over w as num...window w as (partition by paydate order by a.total) 对数据按 paydate 分组,并按 total 降序排序进行编号。
由于按天分组,按交易金额降序进行编号,所以每个分组中的第一条就是交易额最高的,最终过滤 num=1 的数据即可得出每天交易金额最高的用户。
sql 如下:
SELECT *
FROM
(SELECT a.name,4
a.paydate,
row_number()
OVER w AS num
FROM
(SELECT u.name,
paydate,
sum(amount) AS total
FROM user u
JOIN transaction t
ON u.id=t.userid
GROUP BY u.name,paydate) a window w AS (partition by paydate
ORDER BY a.total)) aa
WHERE num=1;
+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐+
| name | paydate | num |
+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐+
| 王五 | 2020‐07‐01 | 1 |
| 王五 | 2020‐07‐03 | 1 |
| 王五 | 2020‐07‐10 | 1 |
| 张三 | 2020‐07‐14 | 1 |
| 张三 | 2020‐07‐30 | 1 |
+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐+
5 rows in set (0.00 sec)
通过如上的两个例子可以看出,窗口函数能够方便的实现一些排序及统计功能,当然也不局限于使用窗口函数,不过实现的 sql 语句可能会比较复杂。
四、窗口函数一览
MySQL 8.0 新增的窗口函数如下:
CUME_DIST()
DENSE_RANK()
FIRST_VALUE()
LAG()
LAST_VALUE()6
LEAD()
NTH_VALUE()
NTILE()
PERCENT_RANK()
RANK()
ROW_NUMBER()
4.1 CUME_DIST() 使用举例
cume_dist():表示当前行及小于当前行在窗口分区总行数中的占比。
举例:
mysql> select * from user1;
+‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| id | name | address | createtime |
+‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 1 | aa | cun | 2020‐06‐01 00:00:00 |
| 2 | bb | cun | 2020‐06‐01 00:00:00 |
| 3 | bb | shi | 2020‐06‐01 01:00:00 |
| 4 | bb | shi | 2020‐06‐01 01:00:00 |
| 5 | cc | cun | 2020‐06‐01 01:00:00 |
| 6 | tt | cun | 2020‐06‐03 01:00:00 |
| 7 | eee | cun | 2020‐06‐04 01:00:00 |
| 8 | eee | cun | 2020‐06‐04 01:00:00 |
| 9 | xx | shen | 2020‐06‐02 01:00:00 |
+‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
9 rows in set (0.00 sec)
SELECT id,
CUME_DIST()
OVER w AS ll,createtime
FROM user1 window w AS (order by createtime);
+‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| id | ll | createtime |
+‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 1 | 0.2222222222222222 | 2020‐06‐01 00:00:00 |
| 2 | 0.2222222222222222 | 2020‐06‐01 00:00:00 |
| 3 | 0.5555555555555556 | 2020‐06‐01 01:00:00 |
| 4 | 0.5555555555555556 | 2020‐06‐01 01:00:00 |
| 5 | 0.5555555555555556 | 2020‐06‐01 01:00:00 |
| 9 | 0.6666666666666666 | 2020‐06‐02 01:00:00 |
| 6 | 0.7777777777777778 | 2020‐06‐03 01:00:00 |
| 7 | 1 | 2020‐06‐04 01:00:00 |
| 8 | 1 | 2020‐06‐04 01:00:00 |
+‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
9 rows in set (0.00 sec)
如上 sql 所示,查看第一行数据值为 0.2222222222222222,因为小于等于 id=1 对应的 createtime 值有两行(id=1 和 id=2),所以经过 cume_dist() 函数计算后为 0.2222222222222222。
其他函数详细解析可前往官网查看:
https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- composer 下载太慢,无法完成安装
- 数字签名与数字证书
- Android TreeView实现带复选框树形组织结构
- Discuz! Q在Windows下CA根证书库错误
- 6种查看Linux进程占用端口号的方法详解
- Flutter 侧滑栏及城市选择UI的实现方法
- 使用iptable和Firewalld工具来管理Linux防火墙连接规则
- 如何使用Flutter开发一款电影APP详解
- Linux获取10个你最常用的终端命令列表
- Flutter banner_view 轮播图的使用及实现代码
- Linux下截屏并编辑的最佳工具
- Android Native 内存泄漏系统化解决方案
- flutter ExpansionTile 层级菜单的实现
- Ubuntu下如何创建XFS文件系统的LVM详解
- 华为开源内部OpenJDK定制版:“毕昇 JDK 8”