打开网易新闻 查看更多图片

作者:赵佳慧

爱可生售后团队成员,主要负责公司运维平台故障诊断。喜爱技术,努力在 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 |

row_number() over(order by createtime) as cr 这部分为窗口函数。over(order by createtime)为窗口规范,函数 row_number() 即对窗口的数据进行编号。所以上述 sql 的意思为:先对 createtime 进行排序,然后对每行数据进行编号。

三、窗口函数的适用场景

下面举例说明在哪些场景下适用窗口函数。

3.1 数据准备

1)创建用户表 user 并插入数据。

+‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+

| 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 场景二

在拥有用户表和交易表的前提下,可以计算出每天交易金额位于第一的用户。

a.paydate,

row_number()

OVER w AS num

paydate,

sum(amount) AS total

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 的数据即可得出每天交易金额最高的用户。

SELECT *

(SELECT a.name,4

ORDER BY a.total)) aa

WHERE num=1;

通过如上的两个例子可以看出,窗口函数能够方便的实现一些排序及统计功能,当然也不局限于使用窗口函数,不过实现的 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 |

SELECT id,

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 |

如上 sql 所示,查看第一行数据值为 0.2222222222222222,因为小于等于 id=1 对应的 createtime 值有两行(id=1 和 id=2),所以经过 cume_dist() 函数计算后为 0.2222222222222222。