暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Window function 的实现

原创 手机用户2895 2025-03-27
232

Window function 的语义

在一个 SELECT 语句里,window function 作用于 GROUP BY、HAVING之后,SELECT DISTINCT、ORDER BY、LIMIT 之前,其主要作用是,对输入的每一行数据,设定一个包含前后数据的“窗口”,在这个窗口内计算聚合函数以及一些特定的窗口函数的值。

这里通过一个例子展示窗口函数的用途。

mysql> SELECT
-> year, country, product, profit,
-> SUM(profit) OVER(PARTITION BY country) AS country_profit
-> FROM sales
-> ORDER BY country, year, product, profit;
+------+---------+---------+--------+----------------+
| year | country | product | profit | country_profit |
+------+---------+---------+--------+----------------+
| 1999 | FINLAND | P0 | 100 | 300 |
| 2000 | FINLAND | P1 | 100 | 300 |
| 2001 | FINLAND | P2 | 100 | 300 |
| 1999 | UK | P0 | 100 | 100 |
| 1999 | USA | P0 | 100 | 200 |
| 2000 | USA | P1 | 100 | 200 |
+------+---------+---------+--------+----------------+

上面的 SELECT 获取了 sales 表里面的所有数据,并通过窗口函数 SUM(profit) OVER(PARTITION BY country) 为每一行添加了“当前这一行所对应的 country 的 total profit”,也就是说,这个窗口“覆盖”了当前这一行所对应 country 的所有数据;

为了避免 window function / window operator / window expression / window 等称呼的歧义,我们把SUM(profit) OVER (PARTITION BY country) 这样的 clause 称为 window function(窗口函数),它的实现称为 window operatorSUM(profit) 这样的表达式称为 window expression (window 表达式)(PARTITION BY country) 这样一个窗口的定义称为 window(窗口)

在窗口函数里,如何定义“窗口”,是关键。一个窗口的定义包含“三要素”(partition、order、frame):

window_spec:
[window_name] [partition_clause] [order_clause] [frame_clause]

比如:

(PARTITION BY column_set ORDER BY column_set ROWS BETWEEN ... )
或者
(PARTITION BY column_set ORDER BY column_set RANGE BETWEEN ...)

用户通过

  • PARTITION BY 定义如何对输入数据进行分区;如果没有定义则所有输入数据成为一个分区;
  • ORDER BY 定义每个分区里的数据如何排序;如果没有定义则默认不排序;
  • ROWS BETWEEN / RANGE BETWEEN 定义窗口起始和结束位置,也称为一个 "frame";如果没有定义 frame 且没有 order by 则默认的 frame 是整个分区;如果没有定义 frame 但是定义了 order by 则默认的 frame 是分区第一行到 current row;需要注意,每一行的 frame 都不能越过这一行所在的分区;

下图中展示了窗口的几个概念:首先将输入数据按照 partition by 进行分区,然后按照 order by 对每个分区的数据进行排序,最后通过 ROWS BETWEEN 或者 RANGE BETWEEN 为当前行指定窗口的起始和结束;图中灰色的一行就是当前行(current row),当窗口滑动到下一行时,下一行变成当前行,窗口的起始和结束也随之变化;

其中,frame 的定义有两种方式

  • ROWS BETWEEN 选择前后几行;比如 ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING 表示往前 3 行到往后 3 行,一共 7 行数据 (如果前面或者后面遇到了 partition 的边界,则少于 7 行)
  • RANGE BETWEEN 根据数据范围选择前后几行;比如 RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING 表示所有值在 [val−3, val+3] 这个范围内的行,val 为当前行的值 (同样地,也不能越过 partition 的边界)

通过上述窗口的定义,用户就可以通过

SELECT
col0,
col1,
window_expr0(...) over (window0),
window_expr1(...) over (window1)
FROM
tables;

为 tables 中的每一行增加一个作用于 window0 的 window_expr0 的值,以及作用于 window1 的 window_expr1 的值;MYSQL 支持的所有窗口表达式在这里可以找到;另外,大多数的聚合函数都可以用作窗口表达式。

不难看出,窗口函数与 aggr/groupby 操作的区别是:aggr / groupby 操作会将多行聚合为一行或几行,而窗口函数不改变数据的行数,只会为每一行增加一个或多个窗口表达式的值。

窗口函数只能出现于 SELECT 以及 ORDER BY clause 中。

关于窗口函数,可以参考 MySQL 官方文档对 window function 的描述:https://dev.mysql.com/doc/refman/8.0/en/window-functions.html这篇 paper(的前几个章节)也是不错的参考:Efficient Processing of Window Functions in Analytical SQL Queries

MySQL 对 window function 的支持

SQL 标准里面定义的 window function 的语法比较灵活,然而 mysql (以及其他一些商业数据库)并不完全支持所有的 window function 语义[MYSQL LIMITATION AND RESTRICTIONS];这意味着 IMCI 不需要实现所有的 window function 的语义,能够简化我们的设计;

在 MySQL 不支持的相关特性中,下列三条是比较重要的:

  1. aggregate window expression 中,不能带有 DISTINCT;i.e., 不支持 SUM(DISTINCT col0) OVER (window) 这样的语义;
  2. 不支持根据 current row 的值动态指定 frame 的边界;i.e., 不支持 ORER BY col ROWS BETWEEN col - 1 AND col + 1; 这样的窗口;
  3. 不支持嵌套的 window function

简单地对比一下其他数据库对 window function 的支持:

SQL SERVER

SQL SERVER 也不支持上面 3 条特性;总体上来说,SQL SERVER 对 window function 的支持度甚至没有 mysql 高 [SQL SERVER LIMITATION AND RESTRICTIONS]。

SQL SERVER 甚至只能在 RANGE BETWEEN 中使用 UNBOUNDED 和 CURRENT ROW 来指定 frame 的起始和结束i.e., 不支持 RANGE BETWEEN 1 PRECEDING AND CURRENT ROW 这样的操作(这基本上就等同于不支持 RANGE BETWEEN 这种模式了,因为可以用 ROWS BETWEEN 来表达同样的语义);这个约束使得它的所有 frame 的起始和结束位置都与 current row 的数据没有关系,大大简化了它对 window function 的实现;

实际上,SQL SERVER 里面甚至没有一个 window operator,它只增加了一个“window spool”即可实现 window function;

POSTGRESQL

文档和使用体验来看,PG 对 window function 的支持程度跟 MySQL 差不多;同样不支持上面 3 条特性;

ORACLE

文档和使用体验来看,相比于上述三款数据库,oracle 对 window function 的支持程度是最高的;支持 aggregate window function 中带有 DISTINCT;支持根据 current row 的值动态指定 frame 的边界;不过也不支持嵌套的 window function;


「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论