MySQL8.0 窗口函数 range vs rows

Last Modified: 2023/08/11

概述

本文主要是为了讲解 MySQL8 中的窗口函数中 frame 单位 range 和 rows 的不同点, 为了方便叙述,假设有一张销售额表,记录店铺销售额,以下数据为店铺 shop1 和 shop2 第二季度的销售额,数据如下:

注:为了让大家更方便的心算,所以特地将销售额的数值做成很小数字。

窗口

window_function_name(expression) OVER (
  [partition_definition]
  [order_definition]
  [frame_definition]
)

窗口是由 partition_definition 定义的,举个例子:

select period as '月份', shop as '店铺', revenue as '月销售额',
       sum(revenue) over(partition by shop) as '季度销售总额'
from sales

上面的 sql 中使用了 shop 作为窗口定义的依据,由于结果集中只有两个 shop(shop1 和 shop2),因此结果集被分为两个窗口,sum 函数是作用于窗口的,查询结果如下:

frame

之前说窗口函数作用于窗口,但是确切的说窗口函数是作用于 frame 的,如果将窗口看做满足特定条件的行的集合,那么 frame 则是窗口的子集。一个 frame 是相对于 CURRENT ROW 来定义的。来个例子,下面的 frame 表达的含义为窗口开始到当前行所包含的所有行。

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

由于 frame 是基于 CURRENT ROW 定义的,因此该 frame 也可以简写成下面的形式:

ROWS UNBOUNDED PRECEDING

range vs rows

MySQL 官方文档中指出了 range 和 rows 的不同:

The frame_units value indicates the type of relationship between the current row and frame rows:

ROWS: The frame is defined by beginning and ending row positions. Offsets are differences in row numbers from the current row number.

RANGE: The frame is defined by rows within a value range. Offsets are differences in row values from the current row value.

但是这个描述属实难以理解,仍然以店铺销售额的数据为例,我们分别使用 rows 和 range 来定义 frame,看看查询结果有何不同:

SELECT period, shop, revenue,
SUM(revenue) OVER(
    PARTITION BY shop
    ORDER BY period ASC
    ROWS UNBOUNDED PRECEDING
) AS '累计销售额'
FROM sales;

官方文档中指出,ROWS 定义了开始行和结束行的位置,开始行和结束行所包含的所有行组成了当前 frame。对照上面的查询语句,开始行为 UNBOUNDED PRECEDING,由于结束行没有定义,默认为 CURRENT ROW

以上图的窗口1为例说明,窗口1中总共包含了三行,编号分别为1、2、3。第1行的是窗口的开始,第3行是窗口的结束。下面的表格给出了窗口1中的每一行对应的 frame 所包含的行,以及窗口函数的计算结果:

行号 frame 包含的行 sum(revenue) 说明
1 1 4 当前行为窗口的第1行,因此 UNBOUNDED PRECEDING 不包含任何行
2 1,2 9 当前行为窗口的第2行,因此 UNBOUNDED PRECEDING 为行1,因此 frame 包含第1和第2行
3 1,2,3 12 当前行为窗口的第3行,因此 UNBOUNDED PRECEDING 为行1和行2,因此 frame 包含第1和第2行和第3行

注:UNBOUNDED PRECEDING 表示窗口开始位置。

上面是 ROWS 的查询结果,接下来使用 RANGE 看查询结果有何不同:

SELECT period, shop, revenue,
SUM(revenue) OVER(
    PARTITION BY shop
    ORDER BY period ASC
    RANGE UNBOUNDED PRECEDING
) AS '累计销售额'
FROM sales;

官方文档中指出,RANGE 依据行的值来定义开始行和结束行的位置。问题是一个行中有多个值,依据哪一列的值?答案是依据 over 字句中的 order by 列的值。具体到我们这里,就是依据 period 列的值来确定逻辑行,先看下图:

下面的表格给出了窗口2中的每一行对应的 frame 所包含的行,以及窗口函数的计算结果:

物理行号 逻辑行号 frame 包含的行 sum(revenue) 说明
4 1 4 1 当前行为4,UNBOUNDED PRECEDING 不包含任何行
5 2 4,5,6 2 当前行为5,实际包含第5第6两行,UNBOUNDED PRECEDING 为行4,因此 frame 包含第4、5、6
6 2 4,5,6 2 当前行为6,实际包含第5第6两行,UNBOUNDED PRECEDING 为行4,因此 frame 包含第4、5、6
7 3 4,5,6,7 5 当前行为7,UNBOUNDED PRECEDING 为行4、5、6,因此 frame 包含4、5、6、7

只有理解了 frame 包含了哪些物理行,才能理解 sum 最后的计算结果。第5和第6行包含的物理行是相同的,因此他们的 sum 结果也是相同的。

总结

  • ROWS 依据行号来区分行,并以此来确定 frame 的开始和结束位置;
  • RANGE 依据 OVER 表达式中 ORDER BY 列的值来区分行,值相同的行在逻辑上被认为是同一行。并以此逻辑行来确定 frame 的开始和结束位置。
有问题吗?点此反馈!

温馨提示:反馈需要登录