二十一、SQL 数据分析基础与进阶:窗口函数

news2024/12/26 1:18:04

文章目录

  • 一、什么是窗口函数
  • 二、聚合函数+OVER()函数
  • 三、PARTITION BY 子句
  • 四、排序函数
    • 4.1 ROW_NUMBER() 函数
    • 4.2 演示 RANK()、DENSE_RANK()、ROW_NUMBER() 函数的异同
    • 4.3 NTILE() 函数
    • 4.4 LAG() 和 LEAD() 函数
    • 4.5 FIRST_VALUE() 和 LAST_VALUE() 函数
  • 五、Window Frames 自定义窗口

本文将详细介绍多种窗口函数。使用窗口函数简化了数据分析工作中查询语句的书写。在没有窗口函数之前,我们需要通过定义临时变量和大量的子查询才能完成工作,而使用窗口函数实现起来更加简洁高效。

窗口函数 是数据分析工作中必须掌握的工具,在 SQL 笔试中也是高频考点

一、什么是窗口函数

窗口函数也称 OLAP 函数,OLAP 的全称是 Online Analytical Processing,可以对数据进行实时分析处理。MySQL 从 8.0 版本开始支持窗口函数。窗口函数的基本语法如下:

<窗口函数> OVER ([PARTITION BY <用于分组的列>] ORDER BY <用于排序的列>)
-- ① 窗口函数: 聚合函数中的 SUM()、AVG()、COUNT()、MAX()、MIN() 其他专用窗口函数 RANK()、DENSE_RANK()、ROW_NUMBER()
-- ② PARTITION BY:你只需将它看成GROUP BY子句,但是在窗口函数中,你要写PARTITION BY
-- ③ ORDER BY:ORDER BY和普通查询语句中的ORDER BY没什么不同。注意,输出的顺序要仔细考虑

补充:

窗口函数与数据分组类似,但是比数据分组功能更加丰富。数据分组是将组内多个数据聚合成一个值,而窗口函数除了可以将组内数据聚合成一个值,还可以保留原始的每条数据。

数据准备:

mysql> SELECT * FROM chapter11;
-- shopname: 每个店铺的名称 sales: 销量 sale_date: 销售日期
+----------+-------+-----------+
| shopname | sales | sale_date |
+----------+-------+-----------+
| A        | 1     | 2020/1/1  |
| B        | 3     | 2020/1/1  |
| C        | 5     | 2020/1/1  |
| A        | 7     | 2020/1/2  |
| B        | 9     | 2020/1/2  |
| C        | 2     | 2020/1/2  |
| A        | 4     | 2020/1/3  |
| B        | 6     | 2020/1/3  |
| C        | 8     | 2020/1/3  |
+----------+-------+-----------+
9 rows in set (0.00 sec)

二、聚合函数+OVER()函数

【练习1】查看每个店铺每天的销量与这张表中全部销量的平均值之间的情况。之前的写法:

-- 全部销量的平均值其实是一个固定的值,固定的值是由查询出来的
mysql> SELECT shopname,sales,sale_date,(SELECT AVG(sales) FROM chapter11) AS avg_sales FROM chapter11;
+----------+-------+-----------+-----------+
| shopname | sales | sale_date | avg_sales |
+----------+-------+-----------+-----------+
| A        | 1     | 2020/1/1  |         5 |
| B        | 3     | 2020/1/1  |         5 |
| C        | 5     | 2020/1/1  |         5 |
| A        | 7     | 2020/1/2  |         5 |
| B        | 9     | 2020/1/2  |         5 |
| C        | 2     | 2020/1/2  |         5 |
| A        | 4     | 2020/1/3  |         5 |
| B        | 6     | 2020/1/3  |         5 |
| C        | 8     | 2020/1/3  |         5 |
+----------+-------+-----------+-----------+
9 rows in set (0.00 sec)

【练习2】查看每个店铺每天的销量与这张表中全部销量的平均值之间的情况。聚合函数+over:

mysql> SELECT shopname,sales,sale_date, AVG(sales) OVER() AS avg_sales FROM chapter11;
-- 运行上面的代码,得到的结果与运行第一种代码完全相同
-- OVER()函数的作用: 将聚合结果显示在每条单独的记录中
+----------+-------+-----------+-----------+
| shopname | sales | sale_date | avg_sales |
+----------+-------+-----------+-----------+
| A        | 1     | 2020/1/1  |         5 |
| B        | 3     | 2020/1/1  |         5 |
| C        | 5     | 2020/1/1  |         5 |
| A        | 7     | 2020/1/2  |         5 |
| B        | 9     | 2020/1/2  |         5 |
| C        | 2     | 2020/1/2  |         5 |
| A        | 4     | 2020/1/3  |         5 |
| B        | 6     | 2020/1/3  |         5 |
| C        | 8     | 2020/1/3  |         5 |
+----------+-------+-----------+-----------+
9 ROWS IN SET (0.00 sec)

三、PARTITION BY 子句

【练习3】每个店铺每天的销量和表中自己店铺的所有销量的平均值进行比较。

-- 分析其实就是按照店铺进行分组,然后在组内进行平均值聚合运算
mysql> SELECT
    ->   cha1.shopname
    ->   ,cha1.sales
    ->   ,cha1.sale_date
    ->   ,cha2.avg_sales
    -> FROM
    ->   chapter11 cha1
    ->   INNER JOIN
    ->     (SELECT
    ->       shopname,
    ->       AVG (sales) AS avg_sales
    ->     FROM
    ->       chapter11
    ->     GROUP BY shopname) cha2
    ->     ON cha1.shopname = cha2.shopname;

-- 最后会得到每个店铺每天的销量,以及该店铺自己所有销量的平均值,具体运行结果如下表所示: 
+----------+-------+-----------+-----------+
| shopname | sales | sale_date | avg_sales |
+----------+-------+-----------+-----------+
| A        | 4     | 2020/1/3  |         4 |
| A        | 7     | 2020/1/2  |         4 |
| A        | 1     | 2020/1/1  |         4 |
| B        | 6     | 2020/1/3  |         6 |
| B        | 9     | 2020/1/2  |         6 |
| B        | 3     | 2020/1/1  |         6 |
| C        | 8     | 2020/1/3  |         5 |
| C        | 2     | 2020/1/2  |         5 |
| C        | 5     | 2020/1/1  |         5 |
+----------+-------+-----------+-----------+
9 rows in set (0.00 sec)

PARTITION BY 的作用与 GROUP BY 类似,在 OVER() 函数中使用 PARTITION BY 来指明要按照哪列进行分组,然后聚合函数就会在分好的组内进行聚合运算。

【练习4】每个店铺每天的销量和表中自己店铺的所有销量的平均值进行比较。具体实现代码如下:

mysql> SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   AVG (sales) OVER (PARTITION BY shopname) AS avg_sales
    -> FROM
    ->   chapter11;

+----------+-------+-----------+-----------+
| shopname | sales | sale_date | avg_sales |
+----------+-------+-----------+-----------+
| A        | 1     | 2020/1/1  |         4 |
| A        | 7     | 2020/1/2  |         4 |
| A        | 4     | 2020/1/3  |         4 |
| B        | 3     | 2020/1/1  |         6 |
| B        | 9     | 2020/1/2  |         6 |
| B        | 6     | 2020/1/3  |         6 |
| C        | 5     | 2020/1/1  |         5 |
| C        | 2     | 2020/1/2  |         5 |
| C        | 8     | 2020/1/3  |         5 |
+----------+-------+-----------+-----------+
9 rows in set (0.00 sec)

四、排序函数

4.1 ROW_NUMBER() 函数

ROW_NUMBER() 函数用来生成每条记录对应的行数,即第几行。行数是按照数据存储的顺序进行生成的,且从 1 开始。

因为行数是按照数据存储顺序生成的,所以一般 ROW_NUMBER() 函数与 ORDER BY 结合使用,此时的行数就表示排序,ROW_NUMBER() 函数的结果中不会出现重复值,即不会出现重复的行数,如果有两个相同的值,会按照表中存储的顺序来生成行数。

【练习5】获取全表中销量的升序排列结果,具体实现代码如下:

mysql> SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   ROW_NUMBER() OVER (ORDER BY sales) AS sales_row_number
    -> FROM
    ->   chapter11;
+----------+-------+-----------+------------------+
| shopname | sales | sale_date | sales_row_number |
+----------+-------+-----------+------------------+
| A        | 1     | 2020/1/1  |                1 |
| C        | 2     | 2020/1/2  |                2 |
| B        | 3     | 2020/1/1  |                3 |
| A        | 4     | 2020/1/3  |                4 |
| C        | 5     | 2020/1/1  |                5 |
| B        | 6     | 2020/1/3  |                6 |
| A        | 7     | 2020/1/2  |                7 |
| C        | 8     | 2020/1/3  |                8 |
| B        | 9     | 2020/1/2  |                9 |
+----------+-------+-----------+------------------+
9 rows in set (0.00 sec)

【练习6】获取各自组内的一个排名结果,具体实现代码如下:

mysql> SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   ROW_NUMBER() OVER (PARTITION BY shopname ORDER BY sales) AS sales_row_number
    -> FROM
    ->   chapter11;

-- 得到每个店铺在不同时间的销量对应的排名,具体运行结果如下表所示: 
+----------+-------+-----------+------------------+
| shopname | sales | sale_date | sales_row_number |
+----------+-------+-----------+------------------+
| A        | 1     | 2020/1/1  |                1 |
| A        | 4     | 2020/1/3  |                2 |
| A        | 7     | 2020/1/2  |                3 |
| B        | 3     | 2020/1/1  |                1 |
| B        | 6     | 2020/1/3  |                2 |
| B        | 9     | 2020/1/2  |                3 |
| C        | 2     | 2020/1/2  |                1 |
| C        | 5     | 2020/1/1  |                2 |
| C        | 8     | 2020/1/3  |                3 |
+----------+-------+-----------+------------------+
9 rows in set (0.00 sec)

【练习7】获取每个店铺销量最差的一天,具体实现代码如下:

mysql> SELECT a.shopname,a.sales,a.sale_date FROM
    -> (SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   ROW_NUMBER() OVER (PARTITION BY shopname ORDER BY sales) AS sales_row_number
    -> FROM
    ->   chapter11) AS a WHERE a.sales_row_number=1;
+----------+-------+-----------+
| shopname | sales | sale_date |
+----------+-------+-----------+
| A        | 1     | 2020/1/1  |
| B        | 3     | 2020/1/1  |
| C        | 2     | 2020/1/2  |
+----------+-------+-----------+
3 rows in set (0.00 sec)

4.2 演示 RANK()、DENSE_RANK()、ROW_NUMBER() 函数的异同

创建订单内容表(order_content)并插入数据,代码如下:

-- ① 创建订单内容表order_content
DROP TABLE IF EXISTS order_content;
CREATE TABLE order_content (
  order_id VARCHAR (8), -- 订单号
  user_id VARCHAR (8), -- 下单用户
  order_price INT -- 订单金额
);
-- ② 插入数据
INSERT INTO order_content (order_id,user_id,order_price) 
VALUES ('o001','u001',800) 
,('o002','u001',800) 
,('o003','u001',1000) 
,('o004','u001',1200) 
,('o005','u002',400) 
,('o006','u002',1500) 
,('o007','u002',2100) 
,('o008','u003',900) 
,('o009','u003',700) 
,('o010','u003',1700);

示例代码如下:

mysql> SELECT *,
    ->     RANK() OVER(PARTITION BY user_id ORDER BY order_price) AS 'rank',
    ->     DENSE_RANK() OVER(PARTITION BY user_id ORDER BY order_price) AS 'dense_rank',
    ->     ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY order_price) AS 'row_number'
    -> FROM order_content;

-- ① RANK() 函数将排序字段值相同的序号视为一样的,将后面排序字段值不相同的序号跳过相同的排名号往后排
-- ② DENSE_RANK() 函数的功能与 RANK() 函数类似,DENSE_RANK() 函数生成的序号是连续的,而RANK() 函数生成的序号有可能不连续
-- ③ ROW_NUMBER() 函数将查询出来的每一行记录生成一个序号,并依次排序且不会重复。
+----------+---------+-------------+------+------------+------------+
| order_id | user_id | order_price | rank | dense_rank | row_number |
+----------+---------+-------------+------+------------+------------+
| o001     | u001    |         800 |    1 |          1 |          1 |
| o002     | u001    |         800 |    1 |          1 |          2 |
| o003     | u001    |        1000 |    3 |          2 |          3 |
| o004     | u001    |        1200 |    4 |          3 |          4 |
| o005     | u002    |         400 |    1 |          1 |          1 |
| o006     | u002    |        1500 |    2 |          2 |          2 |
| o007     | u002    |        2100 |    3 |          3 |          3 |
| o009     | u003    |         700 |    1 |          1 |          1 |
| o008     | u003    |         900 |    2 |          2 |          2 |
| o010     | u003    |        1700 |    3 |          3 |          3 |
+----------+---------+-------------+------+------------+------------+
10 rows in set (0.00 sec)

4.3 NTILE() 函数

NTILE() 函数主要用于对整张表的数据进行切片分组,默认是在对表不进行任何操作之前进行切片分组,比如,现在 chapter11 这张表有 9 行数据,要分成 3 组,那么就是第 1~3 行为一组、第 4~6 行为一组、第 7~9 行为一组。我们将 chapter11 表切分成 3 组,具体实现代码如下:

mysql> SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   NTILE (3) OVER () AS cut_group
    -> FROM
    ->   chapter11;

-- 对全表在不进行任何操作的情况下,按照从前往后分成若干组这种操作的一个使用场景就是针对全表进行随机分组
-- NTILE (3) 函数里面的3可以改成任何值
+----------+-------+-----------+-----------+
| shopname | sales | sale_date | cut_group |
+----------+-------+-----------+-----------+
| A        | 1     | 2020/1/1  |         1 |
| B        | 3     | 2020/1/1  |         1 |
| C        | 5     | 2020/1/1  |         1 |
| A        | 7     | 2020/1/2  |         2 |
| B        | 9     | 2020/1/2  |         2 |
| C        | 2     | 2020/1/2  |         2 |
| A        | 4     | 2020/1/3  |         3 |
| B        | 6     | 2020/1/3  |         3 |
| C        | 8     | 2020/1/3  |         3 |
+----------+-------+-----------+-----------+
9 rows in set (0.00 sec)

【练习8】针对组内进行切片分组。

mysql> SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   NTILE (3) OVER (PARTITION BY shopname) AS cut_group
    -> FROM
    ->   chapter11;

-- 运行上面代码,会在每个组shopname内进行分组,将各自组内的所有记录再切片分成三组
+----------+-------+-----------+-----------+
| shopname | sales | sale_date | cut_group |
+----------+-------+-----------+-----------+
| A        | 1     | 2020/1/1  |         1 |
| A        | 7     | 2020/1/2  |         2 |
| A        | 4     | 2020/1/3  |         3 |
| B        | 3     | 2020/1/1  |         1 |
| B        | 9     | 2020/1/2  |         2 |
| B        | 6     | 2020/1/3  |         3 |
| C        | 5     | 2020/1/1  |         1 |
| C        | 2     | 2020/1/2  |         2 |
| C        | 8     | 2020/1/3  |         3 |
+----------+-------+-----------+-----------+
9 rows in set (0.00 sec)

【练习9】按照指定顺序进行切片分组。比如:在各个组内按照销量进行升序排列以后再进行切片分组,具体实现代码如下:

mysql> SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   NTILE (3) OVER (PARTITION BY shopname ORDER BY sales) AS cut_group
    -> FROM
    ->   chapter11;

-- 运行上面的代码,会先按照shopname列进行分组,然后在组内按照销量进行升序排列,最后进行切片分组,具体运行结果如下表所示: 
+----------+-------+-----------+-----------+
| shopname | sales | sale_date | cut_group |
+----------+-------+-----------+-----------+
| A        | 1     | 2020/1/1  |         1 |
| A        | 4     | 2020/1/3  |         2 |
| A        | 7     | 2020/1/2  |         3 |
| B        | 3     | 2020/1/1  |         1 |
| B        | 6     | 2020/1/3  |         2 |
| B        | 9     | 2020/1/2  |         3 |
| C        | 2     | 2020/1/2  |         1 |
| C        | 5     | 2020/1/1  |         2 |
| C        | 8     | 2020/1/3  |         3 |
+----------+-------+-----------+-----------+
9 rows in set (0.00 sec)

4.4 LAG() 和 LEAD() 函数

lag() 函数:让数据向后移动。ps: 返回当前字段前n行的数据,示例代码如下:
在这里插入图片描述
lead() 函数:让数据向前移动。ps: 返回当前字段后n行的数据,示例代码如下:
在这里插入图片描述
【示例】获取每个店铺本次销量与它前一次销量之差,只需要把该店铺的销量数据全部向后移动1行,这样本次销量数据就与前一次销量数据处于同一行,然后就可以直接做差进行比较了,具体实现代码如下:

mysql> SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   LAG(sales, 1) OVER (PARTITION BY shopname ORDER BY sale_date) lag_value
    -> FROM
    ->   chapter11;

-- 对全表数据按照shopname列进行分组,然后在组内按照销售日期进行排序
-- 将每个店铺的本次销量与它的前一次销量进行比较,所以需要再将分组排序后的数据整体向后移动1行
+----------+-------+-----------+-----------+
| shopname | sales | sale_date | lag_value |
+----------+-------+-----------+-----------+
| A        | 1     | 2020/1/1  | NULL      |
| A        | 7     | 2020/1/2  | 1         |
| A        | 4     | 2020/1/3  | 7         |
| B        | 3     | 2020/1/1  | NULL      |
| B        | 9     | 2020/1/2  | 3         |
| B        | 6     | 2020/1/3  | 9         |
| C        | 5     | 2020/1/1  | NULL      |
| C        | 2     | 2020/1/2  | 5         |
| C        | 8     | 2020/1/3  | 2         |
+----------+-------+-----------+-----------+
9 rows in set (0.00 sec)

【示例】获取每个店铺本次销量与它后一次销量之差,只需要把该店铺的销量数据全部向前移动1行即可,这样本次销量数据就与后一次销量数据处于同一行,然后就可以直接做差进行比较了,在代码实现上,只需要把上面代码中的lag换成lead即可,具体如下:

mysql> SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   LEAD(sales, 1) OVER (PARTITION BY shopname ORDER BY sale_date) lag_value
    -> FROM
    ->   chapter11;
+----------+-------+-----------+-----------+
| shopname | sales | sale_date | lag_value |
+----------+-------+-----------+-----------+
| A        | 1     | 2020/1/1  | 7         |
| A        | 7     | 2020/1/2  | 4         |
| A        | 4     | 2020/1/3  | NULL      |
| B        | 3     | 2020/1/1  | 9         |
| B        | 9     | 2020/1/2  | 6         |
| B        | 6     | 2020/1/3  | NULL      |
| C        | 5     | 2020/1/1  | 2         |
| C        | 2     | 2020/1/2  | 8         |
| C        | 8     | 2020/1/3  | NULL      |
+----------+-------+-----------+-----------+
9 rows in set (0.00 sec)

4.5 FIRST_VALUE() 和 LAST_VALUE() 函数

FIRST_VALUE() 和 LAST_VALUE() 顾名思义,就是第一个值和最后一个值,但又不是完全意义上的第一个或最后一个,而是截至当前行的第一个或最后一个。

【示例】获取每个店铺的最早销售日期和截至当前最后一次销售日期,通过这两个指标来反映店铺的营业时间,可以直接借助FIRST_VALUE()和LAST_VALUE()函数,具体实现代码如下:

mysql> SELECT
    ->   shopname,
    ->   sales,
    ->   sale_date,
    ->   FIRST_VALUE(sale_date) OVER (PARTITION BY shopname ORDER BY sale_date) AS first_date,
    ->   LAST_VALUE(sale_date) OVER (PARTITION BY shopname ORDER BY sale_date) AS last_date
    -> FROM
    ->   chapter11;
+----------+-------+-----------+------------+-----------+
| shopname | sales | sale_date | first_date | last_date |
+----------+-------+-----------+------------+-----------+
| A        | 1     | 2020/1/1  | 2020/1/1   | 2020/1/1  |
| A        | 7     | 2020/1/2  | 2020/1/1   | 2020/1/2  |
| A        | 4     | 2020/1/3  | 2020/1/1   | 2020/1/3  |
| B        | 3     | 2020/1/1  | 2020/1/1   | 2020/1/1  |
| B        | 9     | 2020/1/2  | 2020/1/1   | 2020/1/2  |
| B        | 6     | 2020/1/3  | 2020/1/1   | 2020/1/3  |
| C        | 5     | 2020/1/1  | 2020/1/1   | 2020/1/1  |
| C        | 2     | 2020/1/2  | 2020/1/1   | 2020/1/2  |
| C        | 8     | 2020/1/3  | 2020/1/1   | 2020/1/3  |
+----------+-------+-----------+------------+-----------+
9 rows in set (0.00 sec)

【示例】FIRST_VALUE():返回当前第一个值。示例代码如下:
在这里插入图片描述
【示例】LAST_VALUE():返回当前最后一个值。示例代码如下:

mysql> SELECT
    ->   *,
    ->   LAST_VALUE (order_price) OVER (PARTITION BY user_id ORDER BY order_price) AS `last_value`
    -> FROM
    ->   order_content;
+----------+---------+-------------+------------+------------+
| order_id | user_id | order_price | order_date | last_value |
+----------+---------+-------------+------------+------------+
| o001     | u001    |         800 | 2021-06-18 |        800 |
| o002     | u001    |         800 | 2021-06-19 |        800 |
| o003     | u001    |        1000 | 2021-06-22 |       1000 |
| o004     | u001    |        1200 | 2021-06-24 |       1200 |
| o005     | u002    |         400 | 2021-06-25 |        400 |
| o006     | u002    |        1500 | 2021-06-26 |       1500 |
| o007     | u002    |        2100 | 2021-06-28 |       2100 |
| o009     | u003    |         700 | 2021-07-03 |        700 |
| o008     | u003    |         900 | 2021-07-01 |        900 |
| o010     | u003    |        1700 | 2021-07-04 |       1700 |
+----------+---------+-------------+------------+------------+
10 rows in set (0.00 sec)

PS: 当前最后一个值即当前值,所以 order_price 和 last_value 字段的值相同。

【示例】NTH_VALUE():返回有序行的第n小的值。示例代码如下:

mysql> SELECT
    ->   *,
    ->   NTH_VALUE (order_price, 3) OVER (PARTITION BY user_id ORDER BY order_price) AS `nth_value`
    -> FROM
    ->   order_content;

-- NTH_VALUE()函数返回了每个分区第3小的数据,所以NTH_VALUE()函数通常用来求排在第n位的数据。
-- 例如: 查询部门薪资排在第2位的员工信息,而返回排在第n位的数据只需要在order by 之后添加desc
+----------+---------+-------------+------------+-----------+
| order_id | user_id | order_price | order_date | nth_value |
+----------+---------+-------------+------------+-----------+
| o001     | u001    |         800 | 2021-06-18 |      NULL |
| o002     | u001    |         800 | 2021-06-19 |      NULL |
| o003     | u001    |        1000 | 2021-06-22 |      1000 |
| o004     | u001    |        1200 | 2021-06-24 |      1000 |
| o005     | u002    |         400 | 2021-06-25 |      NULL |
| o006     | u002    |        1500 | 2021-06-26 |      NULL |
| o007     | u002    |        2100 | 2021-06-28 |      2100 |
| o009     | u003    |         700 | 2021-07-03 |      NULL |
| o008     | u003    |         900 | 2021-07-01 |      NULL |
| o010     | u003    |        1700 | 2021-07-04 |      1700 |
+----------+---------+-------------+------------+-----------+
10 rows in set (0.00 sec)

五、Window Frames 自定义窗口

可以以当前行为基准,精确的自定义要选取的数据范围。例如:想选取当前行的前三行和后三行一共7行数据进行统计,相当于自定义一个固定大小的窗口,当当前行移动的时候,窗口也会随之移动

看下面的例子,我们选中了当前行的前两行和后两行,一共5行数据
请添加图片描述
定义窗口的语法:

-- 定义 window frames 有两种方式: ROWS 和 RANGE ,具体语法如下:
<window function> OVER (...
    -- ...代表了之前我们介绍的例如 PARTITION BY  子句
    ORDER BY <order_column>
    [ROWS|RANGE] <window frame extent>
    -- 先理解ROWS语法
    -- ① 通用写法 ROWS BETWEEN lower_bound AND upper_bound

上限(upper_bund) 和 下限(lower_bound) 的取值为如下5种情况:

-- ① UNBOUNDED PRECEDING – 对上限无限制
-- ② PRECEDING – 当前行之前的第n行 (n 填入具体数字如: 5 PRECEDING)
-- ③ CURRENT ROW – 仅当前行
-- ④ FOLLOWING –当前行之后的第 n 行 (n 填入具体数字如: 5 FOLLOWING)
-- ⑤ UNBOUNDED FOLLOWING – 对下限无限制

重新创建 order_content 表,增加一列下单日期的字段 order_date,代码如下:

mysql> DROP TABLE IF EXISTS order_content;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE order_content (
    ->   order_id VARCHAR (8),
    ->   user_id VARCHAR (8),
    ->   order_price INT,
    ->   order_date DATE
    -> );
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO order_content (
    ->   order_id,
    ->   user_id,
    ->   order_price,
    ->   order_date
    -> )
    -> VALUES
    ->   ('o001', 'u001', 800, '2021-06-18'),
    ->   ('o002', 'u001', 800, '2021-06-19'),
    ->   ('o003', 'u001', 1000, '2021-06-22'),
    ->   ('o004', 'u001', 1200, '2021-06-24'),
    ->   ('o005', 'u002', 400, '2021-06-25'),
    ->   ('o006', 'u002', 1500, '2021-06-26'),
    ->   ('o007', 'u002', 2100, '2021-06-28'),
    ->   ('o008', 'u003', 900, '2021-07-01'),
    ->   ('o009', 'u003', 700, '2021-07-03'),
    ->   ('o010', 'u003', 1700, '2021-07-04');
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

更新的 order_content 表如下所示:

mysql> SELECT * FROM order_content;
+----------+---------+-------------+------------+
| order_id | user_id | order_price | order_date |
+----------+---------+-------------+------------+
| o001     | u001    |         800 | 2021-06-18 |
| o002     | u001    |         800 | 2021-06-19 |
| o003     | u001    |        1000 | 2021-06-22 |
| o004     | u001    |        1200 | 2021-06-24 |
| o005     | u002    |         400 | 2021-06-25 |
| o006     | u002    |        1500 | 2021-06-26 |
| o007     | u002    |        2100 | 2021-06-28 |
| o008     | u003    |         900 | 2021-07-01 |
| o009     | u003    |         700 | 2021-07-03 |
| o010     | u003    |        1700 | 2021-07-04 |
+----------+---------+-------------+------------+
10 rows in set (0.00 sec)

【练习10】再次演示聚合函数作为窗口函数的情况,代码如下:

mysql> SELECT
    ->   *,
    ->   AVG (order_price) OVER (PARTITION BY user_id ORDER BY order_date) AS current_avg
    -> FROM
    ->   order_content;

-- 按照 user_id 字段进行分区/分组 然后根据 order_date 字段排序
-- ps: current_avg 字段代表当前分区中起始位置到当前位置的order_price平均值 例如: u001的current_avg字段-->800=(800+800)/2 
-- 950=(800+800+1000+1200)/4
+----------+---------+-------------+------------+-------------+
| order_id | user_id | order_price | order_date | current_avg |
+----------+---------+-------------+------------+-------------+
| o001     | u001    |         800 | 2021-06-18 |    800.0000 |
| o002     | u001    |         800 | 2021-06-19 |    800.0000 |
| o003     | u001    |        1000 | 2021-06-22 |    866.6667 |
| o004     | u001    |        1200 | 2021-06-24 |    950.0000 |
| o005     | u002    |         400 | 2021-06-25 |    400.0000 |
| o006     | u002    |        1500 | 2021-06-26 |    950.0000 |
| o007     | u002    |        2100 | 2021-06-28 |   1333.3333 |
| o008     | u003    |         900 | 2021-07-01 |    900.0000 |
| o009     | u003    |         700 | 2021-07-03 |    800.0000 |
| o010     | u003    |        1700 | 2021-07-04 |   1100.0000 |
+----------+---------+-------------+------------+-------------+
10 rows in set (0.00 sec)

-- 第二种写法: 
mysql> SELECT
    ->   *,
    ->   AVG (order_price) OVER (
    ->     PARTITION BY user_id
    -> ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING
    ->   AND current ROW
    -> ) AS current_avg
    -> FROM
    ->   order_content;
+----------+---------+-------------+------------+-------------+
| order_id | user_id | order_price | order_date | current_avg |
+----------+---------+-------------+------------+-------------+
| o001     | u001    |         800 | 2021-06-18 |    800.0000 |
| o002     | u001    |         800 | 2021-06-19 |    800.0000 |
| o003     | u001    |        1000 | 2021-06-22 |    866.6667 |
| o004     | u001    |        1200 | 2021-06-24 |    950.0000 |
| o005     | u002    |         400 | 2021-06-25 |    400.0000 |
| o006     | u002    |        1500 | 2021-06-26 |    950.0000 |
| o007     | u002    |        2100 | 2021-06-28 |   1333.3333 |
| o008     | u003    |         900 | 2021-07-01 |    900.0000 |
| o009     | u003    |         700 | 2021-07-03 |    800.0000 |
| o010     | u003    |        1700 | 2021-07-04 |   1100.0000 |
+----------+---------+-------------+------------+-------------+
10 rows in set (0.00 sec)

-- 简写: ROWS BETWEEN UNBOUNDED PRECEDING AND current ROW --> ROWS UNBOUNDED PRECEDING 与上述结果执行是一致的
SELECT
  *,
  AVG(order_price) OVER (PARTITION BY user_id ORDER BY order_date ROWS UNBOUNDED PRECEDING) AS current_avg
FROM
  order_content;

SUM()、MIN()、MAX() 函数的使用方法相同,此处不再赘述。

【练习11】将当前行和它前面的两行划为一个窗口,AVG()函数作用在这3行上面,并将求出的结果展示在右侧。
在这里插入图片描述
【练习12】将当前行和它前面的一行至后面一行划分为一个窗口,AVG()函数作用在这3行上面,并将求出的结果展示在右侧。

至此今天的学习就到此结束了,笔者在这里声明,笔者写文章只是为了学习交流,以及让更多学习数据库的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!


在这里插入图片描述

    好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
    如果我的博客对你有帮助、如果你喜欢我的博客内容,请 点赞评论收藏 一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
 编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了 关注 我哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/483370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

S5CL:通过分层对比学习统一全监督、自监督和半监督学习

文章目录 S5CL: Unifying Fully-Supervised,Self-supervised, and Semi-supervised Learning Through Hierarchical Contrastive Learning摘要本文方法损失函数 实验结果消融实验 S5CL: Unifying Fully-Supervised,Self-supervised, and Semi-supervised Learning Through Hier…

计算机体系结构框架

这是基于胡伟武老师的计算机体系结构课程所总结出来的框架&#xff0c;希望能让没有学习该课程的人可以去了解计算机是怎么造的&#xff0c;而对于学习这门课程的人可以在学习课程之前对整体框架有一个初步的认知。 如果不想看文字的话&#xff0c;可以看视频哦&#xff01; 目…

ros2 服务——ubuntu20.04——自定义数据类型

文章目录 自定义一个服务数据类型接口创建sev目录和文件修改包的CMakeLists.txt文件修改包的package.xml文件查看是否成功 服务全部代码 自定义一个服务数据类型接口 创建sev目录和文件 服务的接口类型由两部分组成&#xff0c;请求和相应 在包的src的同级目录下创建sev文件…

OpenCV基础补充自适应阈值及图像金字塔

文章目录 OpenCV基础补充自适应阈值及图像金字塔自适应阈值图像金字塔人脸检测视频检测人脸检测 OpenCV基础补充自适应阈值及图像金字塔 对于OpenCV知识点还有很多&#xff0c;基础的大家可以参考前面几节。 OpenCv基础之绘图及几何变换实例 OpenCV基础操作之图像的形态学运算…

PMP/高项 06-项目成本管理

项目成本管理 概念 项目成本管理 项目成本管理又被称为项目造价管理&#xff0c;是有关项目成本和项目价值两个方面的管理&#xff0c;是为保障以最小的成本实现最大的项目价值而开展的项目专项管理工作。 确保在批准的项目预算内完成项目 成本管理内容 规划成本管理 制定项目…

华为OD机试真题(Java),整数对最小和(100%通过+复盘思路)

一、题目描述 给定两个整数数组array1、array2,数组元素按升序排列。 假设从array1、array2中分别取出一个元素可构成一对元素,现在需要取出k对元素, 并对取出的所有元素求和,计算和的最小值。 注意: 两对元素如果对应于array1、array2中的两个下标均相同,则视为同一…

FL Studio21没有language选项?如何设置切换中文语言

音乐在人们心中的地位日益增高&#xff0c;近几年音乐选秀的节目更是层出不穷&#xff0c;喜爱音乐&#xff0c;创作音乐的朋友们也是越来越多&#xff0c;音乐的类型有很多&#xff0c;好比古典&#xff0c;流行&#xff0c;摇滚等等。对新手友好程度基本上在首位&#xff0c;…

第14章 项目采购管理

文章目录 采购管理包括如下几个过程14.2 编制采购计划 462编制采购计划的输出1&#xff09;采购管理计划2&#xff09;采购工作说明书3&#xff09;采购文件 14.2.3 工作说明书&#xff08;SOW&#xff09; 14.3 实施采购 47414.3.2 实施采购的方法和技术 476&#xff08;1&…

基于STM32F103-HAL库-IAR的BOOT和APP编写

前言&#xff1a; 在单片机中&#xff0c;将程序分为boot和app&#xff0c;这样可以实现一些功能&#xff1a;使用串口更新app等等&#xff1b; 需求&#xff1a; 编写boot和sys程序段&#xff0c;分别放在flash内存不同位置&#xff0c;先执行boot然后执行sys&#xff1a;boo…

分享24个强大的HTML属性 —— 建议每位前端工程师都应该掌握

前期回顾 是不是在为 API 烦恼 &#xff1f;好用免费的api接口大全呼之欲出_0.活在风浪里的博客-CSDN博客APi、常用框架、UI、文档—— 整理合并https://blog.csdn.net/m0_57904695/article/details/130459417?spm1001.2014.3001.5501 &#x1f44d; 本文专栏&#xff1a;…

【实例展示通俗易懂】SQL中的内外连接、左右连接

一、分类 连接分为内连接与外连接&#xff1b;外连接分为左连接与右连接。 二、创建两个表格作为例子 AAA&#xff1a; BBB&#xff1a; 三、 外连接 1、左连接 &#xff08;1&#xff09…

「OceanBase 4.1 体验」|快速安装部署

文章目录 一、Oceanbase数据库简介1.1 核心特性1.2 系统架构1.2.1 存储层1.2.2 复制层1.2.3 均衡层1.2.4 事务层1.2.4.1 原子性1.2.4.2 隔离性 1.2.5 SQL 层1.2.5.1 SQL 层组件1.2.5.2 多种计划 1.2.6 接入层 二、OceanBase 数据库社区版部署2.1 部署方式2.2 基础环境配置2.3 通…

SpringCloud学习-实用篇03

以下内容的代码可见&#xff1a;SpringCloud_learn/day03 1.初识Docker 什么是Docker? 项目部署问题&#xff1a;大型项目组件较多&#xff0c;运行环境也较为复杂&#xff0c;部署时会碰到一些问题 依赖关系复杂&#xff0c;容易出现兼容性问题开发、测试、生产环境有差异 Do…

详解C语言中的6个位操作符:按位取反、按位与、按位或、按位异或、左移、右移

本篇博客会讲解C语言中的6个位操作符&#xff1a;按位取反(~)、按位与(&)、按位或(|)、按位异或(^)、左移(<<)、右移(>>)。这6个操作符都是操作整数的二进制位的。在学习这6个位操作符之前&#xff0c;大家需要先掌握“整数在内存中的存储”这个知识点&#xf…

ESP32设备驱动-TSL2591数光转换器驱动

TSL2591数光转换器驱动 文章目录 TSL2591数光转换器驱动1、TSL2591介绍2、硬件准备3、软件准备4、驱动实现1、TSL2591介绍 TSL2591 是一款非常高灵敏度的光数字转换器,可将光强度转换为能够直接 I2C 接口的数字信号输出。 该器件在单个 CMOS 集成电路上结合了一个宽带光电二极…

如何用ChatGPT做会议总结?

该场景对应的关键词库&#xff08;12个&#xff09;&#xff1a; 会议主题、参与人员、讨论议题、关键观点、决策、时间、地点、修改要求、文本格式、语言风格、列表、段落。 提问模板&#xff08;3个&#xff09;&#xff1a; 第一步&#xff1a;用飞书会议等软件整理好会议…

代码随想录算法训练营第四十九天| 121. 买卖股票的最佳时机、122.买卖股票的最佳时机II

文章目录 121. 买卖股票的最佳时机122.买卖股票的最佳时机II 121. 买卖股票的最佳时机 为什么定义dp数组为二维数组&#xff1f; dp数组定义&#xff0c;dp(i)[0] 表示第i天持有股票所得最多现金&#xff0c;dp(i)[1]表示第i天不持有股票的状态&#xff08;未必当前卖出&#x…

《Netty》从零开始学netty源码(五十)之PoolArena的内存分配

目录 tcacheAllocateSmall()tcacheAllocateNormal()newChunk() allocateHuge()newUnpooledChunk() ​PoolArena根据请求大大小主要分配三中类型的内存&#xff0c;小于28KB的分配PoolSubpage&#xff0c;28KB~4MB的分配池化的PoolChunk&#xff0c;4MB以上的分配非池化的内存​…

【C++ 学习 ②】- 类和对象(上)

目录 一、 面向对象的基本理念 1.1 - 什么是对象&#xff1f; 1.2 - 类和对象 1.3 - 面向对象的五条原则 1.4 - 面向过程 vs 面向对象 二、C 中的结构体 三、类的定义 3.1 - 类的两种定义方式 3.2 - 成员变量的命名规范 四、类的访问限定符和封装 4.1 - 访问限定符 …

SPFA + 链式前向星建图【附Java模板】

SPFA算法是什么&#xff1f;它能解决什么样的问题&#xff1f; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 数据结构与算法专栏的文章图文并茂&#x1f995;生动形象&#x1f996;简…