MySQL窗口函数框架设置(Frame子句)

(转载自己在另一博客的文章)

窗口函数:普通聚合函数会将多条记录聚合为一条,但有时候我们希望聚合结果在每一行当中显示,从而实现对聚合结果的运算,为达到该效果,我们通常需要嵌套子查询,而MySQL8.0开始支持窗口函数,可以更便捷地实现这一功能。

窗口函数语法:

window_function_name(expr) 
    OVER([window_name] [partition_clause][order_clause] [frame_clause]);

本文章着重展示窗口函数中的框架设置(frame_clause)

示例—某商店的商品订单

建立数据表

CREATE TABLE `sql_store`.`product_order`  (
  `order_id` int NOT NULL,
  `product` varchar(50) NOT NULL,
  `quantity` int NOT NULL,
  PRIMARY KEY (`order_id`)
);
INSERT INTO `sql_store`.`product_order` (`order_id`, `product`, `quantity`) VALUES ('1', 'apple', '10');
INSERT INTO `sql_store`.`product_order` (`order_id`, `product`, `quantity`) VALUES ('2', 'cereal', '5');
INSERT INTO `sql_store`.`product_order` (`order_id`, `product`, `quantity`) VALUES ('3', 'apple', '5');
INSERT INTO `sql_store`.`product_order` (`order_id`, `product`, `quantity`) VALUES ('4', 'cereal', '3');
INSERT INTO `sql_store`.`product_order` (`order_id`, `product`, `quantity`) VALUES ('5', 'apple', '10');
INSERT INTO `sql_store`.`product_order` (`order_id`, `product`, `quantity`) VALUES ('6', 'bread', '10');
INSERT INTO `sql_store`.`product_order` (`order_id`, `product`, `quantity`) VALUES ('7', 'bread', '12');
INSERT INTO `sql_store`.`product_order` (`order_id`, `product`, `quantity`) VALUES ('8', 'bread', '6');

建立store表如下

+----------+---------+----------+
| order_id | product | quantity |
+----------+---------+----------+
|        1 | apple   |       10 |
|        2 | cereal  |        5 |
|        3 | apple   |        5 |
|        4 | cereal  |        3 |
|        5 | apple   |       10 |
|        6 | bread   |       10 |
|        7 | bread   |       12 |
|        8 | bread   |        6 |
+----------+---------+----------+

默认框架

窗口函数的框架即窗口的范围,可使用[frame_clause]进行设置。由于[frame_clause]是可选参数,如果没有写出,则会设置为默认框架范围,默认框架范围取决于是否进行了排序或分类。

  • 未进行排序或分类(如sum1),则默认框架范围为所有行
SELECT 
    *,
    SUM(quantity) OVER () AS sum1
FROM sql_store.product_order;
+----------+---------+----------+------+
| order_id | product | quantity | sum1 |
+----------+---------+----------+------+
|        1 | apple   |       10 |   61 |
|        2 | cereal  |        5 |   61 |
|        3 | apple   |        5 |   61 |
|        4 | cereal  |        3 |   61 |
|        5 | apple   |       10 |   61 |
|        6 | bread   |       10 |   61 |
|        7 | bread   |       12 |   61 |
|        8 | bread   |        6 |   61 |
+----------+---------+----------+------+

  • 使用 ORDER BY 进行排序,且未进行分类(如sum2),则默认框架范围为第一个分区到所在行的分区
  • 使用 PARTITION BY 进行分类(sum3),则默认框架范围为每个分区的所有行
SELECT 
    *,
    SUM(quantity) OVER (ORDER BY product) AS sum2
    SUM(quantity) OVER (PARTITION BY product) AS sum3
FROM sql_store.product_order;
+----------+---------+----------+------+------+
| order_id | product | quantity | sum2  | sum3 |
+----------+---------+----------+------+------+
|        1 | apple   |       10 |   25 |   25 |
|        3 | apple   |        5 |   25 |   25 |
|        5 | apple   |       10 |   25 |   25 |
|        6 | bread   |       10 |   53 |   28 |
|        7 | bread   |       12 |   53 |   28 |
|        8 | bread   |        6 |   53 |   28 |
|        2 | cereal  |        5 |   61 |    8 |
|        4 | cereal  |        3 |   61 |    8 |
+----------+---------+----------+------+------+

框架基本单位(frame_units)

框架的计数单位分为两种:{ROWS | RANGE},默认值为RANGE

  • ROWS:以一行为一个单位
  • RANGE(有排序):以连续相同的值为一个单位
  • RANGE(无排序):以分区为单位
SELECT 
    *,
    SUM(quantity) OVER (PARTITION BY product ORDER BY quantity ROWS CURRENT ROW) AS sum1,
    SUM(quantity) OVER (PARTITION BY product ORDER BY quantity RANGE CURRENT ROW) AS sum2,
    SUM(quantity) OVER (PARTITION BY product RANGE CURRENT ROW) AS sum3
FROM sql_store.product_order
+----------+---------+----------+------+------+------+
| order_id | product | quantity | sum1 | sum2 | sum3 |
+----------+---------+----------+------+------+------+
|        3 | apple   |        5 |    5 |    5 |   25 |
|        1 | apple   |       10 |   10 |   20 |   25 |
|        5 | apple   |       10 |   10 |   20 |   25 |
|        8 | bread   |        6 |    6 |    6 |   28 |
|        6 | bread   |       10 |   10 |   10 |   28 |
|        7 | bread   |       12 |   12 |   12 |   28 |
|        4 | cereal  |        3 |    3 |    3 |    8 |
|        2 | cereal  |        5 |    5 |    5 |    8 |
+----------+---------+----------+------+------+------+
  • sum1中,使用ROWS作为基本单位,所以对每一行进行单独计算
  • sum2中,使用RANGE作为基本单位,apple 的第二、第三个订单数量都为10,所以第二、第三行视为一个单位
  • sum3中,使用RANGE作为基本单位,并且没有ORDER BY,所以每个分区为一个单位

框架范围(frame_extent)

框架语句分为以下几种:

  • CURRENT ROW --- 当前行
  • UNBOUNDED PRECEDING ---当前行上侧所有行
  • UNBOUNDED FOLLOWING ---当前行下侧所有行
  • expr PRECEDING---当前行上侧expr行(expr可以是数字,也可以是表达式)
  • expr FOLLOWING---当前行下侧expr行(expr可以是数字,也可以是表达式)
窗口范围

框架范围可由两种形式来定义:{frame_start | frame_between},范围的定义基于基本单位。

  • frame_start:仅指定开始行(或区域),则结束范围为默认值,即当前行(或区域)
SELECT 
    *,
    SUM(quantity) OVER (PARTITION BY product ORDER BY quantity ROWS 1 PRECEDING) AS sum
FROM sql_store.product_order
+----------+---------+----------+------+
| order_id | product | quantity | sum  |
+----------+---------+----------+------+
|        3 | apple   |        5 |    5 |
|        1 | apple   |       10 |   15 |
|        5 | apple   |       10 |   20 |
|        8 | bread   |        6 |    6 |
|        6 | bread   |       10 |   16 |
|        7 | bread   |       12 |   22 |
|        4 | cereal  |        3 |    3 |
|        2 | cereal  |        5 |    8 |
+----------+---------+----------+------+

如上图所示,该语句仅设置了frame_start部分(ROWS 1 PRECEDING),即当前行的前一行,而结束行则默认为当前行,所以sum的结果是相邻两行求和。


  • frame_between:指定开始行(或区域)与结束行(或区域)
    • frame_between语句:BETWEEN frame_start AND frame_end
SELECT 
    *,
    SUM(quantity) OVER (PARTITION BY product ORDER BY quantity ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS sum
FROM sql_store.product_order
+----------+---------+----------+------+
| order_id | product | quantity | sum  |
+----------+---------+----------+------+
|        3 | apple   |        5 |    5 |
|        1 | apple   |       10 |   15 |
|        5 | apple   |       10 |   25 |
|        8 | bread   |        6 |    6 |
|        6 | bread   |       10 |   16 |
|        7 | bread   |       12 |   28 |
|        4 | cereal  |        3 |    3 |
|        2 | cereal  |        5 |    8 |
+----------+---------+----------+------+

如上图所示,该框架利用BETWEEN ... AND...语句设置了frame_start(UNBOUNDED PRECEDING)和frame_end(CUREENT ROW),即框架范围为第一行至当前行,实现了累计求和的效果。

你可能感兴趣的:(MySQL窗口函数框架设置(Frame子句))