窗口函数
窗口函数是用于在查询结果集中执行复杂计算的函数。与聚合函数不同,窗口函数不会将多行数据压缩成单行数据,而是在保留行细节的同时进行计算。窗口函数允许在查询结果集中的某一部分(称为窗口或帧)内进行操作,从而实现如累计和、排名、移动平均等复杂计算。
除了常见的聚合函数,窗口函数还有一些特殊的函数。例如,RANK()
、DENSE_RANK()
、ROW_NUMBER()
等函数都是窗口函数特有的,它们可以在数据分析中提供更多的灵活性和便利性。这些函数通常与OVER
子句一起使用,以确定窗口的范围和排序。
其基本语法为:
函数名 OVER ([PARTITION BY 列名] [ORDER BY 列名][ACE|EDSC])
其中函数名
包括聚合函数和窗口函数专用的函数。
假如我们有一张名为 sales 的表,如下所示:
+---------+------------+--------+----------------+
| sale_id | sale_date | amount | salesperson_id |
+---------+------------+--------+----------------+
| 1 | 2024-06-01 | 100.00 | 1 |
| 2 | 2024-06-02 | 150.00 | 2 |
| 3 | 2024-06-02 | 200.00 | 1 |
| 4 | 2024-06-03 | 120.00 | 3 |
| 5 | 2024-06-03 | 180.00 | 2 |
| 6 | 2024-06-04 | 220.00 | 1 |
| 7 | 2024-06-04 | 100.00 | 1 |
| 8 | 2024-06-05 | 150.00 | 2 |
+---------+------------+--------+----------------+
RANK()、DENSE_RANK()、ROW_NUMBER()
先来看一下着三个函数的区别。
函数 | 描述 | 示例 |
---|---|---|
RANK() | RANK() 函数返回唯一的排名值,但在遇到相同的值时会跳过一些排名。例如,如果有两行具有相同的值,则它们都会被赋予相同的排名,但下一个排名值将会跳过。 | 1, 2, 2, 4 |
DENSE_RANK() | DENSE_RANK() 函数也返回唯一的排名值,但不会跳过任何排名。即使有两行具有相同的值,下一个排名值也不会被跳过。 | 1, 2, 2, 3 |
ROW_NUMBER() | ROW_NUMBER() 函数为结果集中的每一行分配一个唯一的数字。即使有两行具有相同的值,它们也会被赋予不同的数字。 | 1, 2, 3, 4 |
下面我们逐一的进行介绍:
-
**RANK():**为相同数值的行分配相同的排名,并跳过下一个排名。
第一种方式:
select sale_id, sale_date, amount, rank() over (partition by sale_date order by amount desc) as sales_rank from sales; +---------+------------+--------+------------+ | sale_id | sale_date | amount | sales_rank | +---------+------------+--------+------------+ | 1 | 2024-06-01 | 100.00 | 1 | | 3 | 2024-06-02 | 200.00 | 1 | | 2 | 2024-06-02 | 150.00 | 2 | | 5 | 2024-06-03 | 180.00 | 1 | | 4 | 2024-06-03 | 120.00 | 2 | | 6 | 2024-06-04 | 220.00 | 1 | | 7 | 2024-06-04 | 100.00 | 2 | | 8 | 2024-06-05 | 150.00 | 1 | +---------+------------+--------+------------+ 8 rows in set (0.00 sec)
第二种方式:
select sale_id, sale_date, amount, rank() over (order by amount desc) as sales_rank from sales; +---------+------------+--------+------------+ | sale_id | sale_date | amount | sales_rank | +---------+------------+--------+------------+ | 6 | 2024-06-04 | 220.00 | 1 | | 3 | 2024-06-02 | 200.00 | 2 | | 5 | 2024-06-03 | 180.00 | 3 | | 2 | 2024-06-02 | 150.00 | 4 | | 8 | 2024-06-05 | 150.00 | 4 | | 4 | 2024-06-03 | 120.00 | 6 | | 1 | 2024-06-01 | 100.00 | 7 | | 7 | 2024-06-04 | 100.00 | 7 | +---------+------------+--------+------------+ 8 rows in set (0.00 sec)
两种方式的结果不同,这是因为
partition by sale_date
,设定了排序的对象范围,才会出现上面的结果。同时从这两个例子中也可以看到
partition by
不是必须的,即使不指定也能正常使用窗口函数。也能看出窗口函数同时具备分组和排序两种功能。 -
**DENSE_RANK():**为相同数值的行分配相同的排名,但不跳过下一个排名。
select sale_id, sale_date, amount, dense_rank() over (order by amount desc) as dense_sales_rank from sales; +---------+------------+--------+------------------+ | sale_id | sale_date | amount | dense_sales_rank | +---------+------------+--------+------------------+ | 6 | 2024-06-04 | 220.00 | 1 | | 3 | 2024-06-02 | 200.00 | 2 | | 5 | 2024-06-03 | 180.00 | 3 | | 2 | 2024-06-02 | 150.00 | 4 | | 8 | 2024-06-05 | 150.00 | 4 | | 4 | 2024-06-03 | 120.00 | 5 | | 1 | 2024-06-01 | 100.00 | 6 | | 7 | 2024-06-04 | 100.00 | 6 | +---------+------------+--------+------------------+ 8 rows in set (0.00 sec)
-
ROW_NUMBER(): 给查询结果中的每一行分配一个唯一的序号。
select sale_id, sale_date, amount, row_number() over (order by sale_date) as row_num from sales; +---------+------------+--------+---------+ | sale_id | sale_date | amount | row_num | +---------+------------+--------+---------+ | 1 | 2024-06-01 | 100.00 | 1 | | 2 | 2024-06-02 | 150.00 | 2 | | 3 | 2024-06-02 | 200.00 | 3 | | 4 | 2024-06-03 | 120.00 | 4 | | 5 | 2024-06-03 | 180.00 | 5 | | 6 | 2024-06-04 | 220.00 | 6 | | 7 | 2024-06-04 | 100.00 | 7 | | 8 | 2024-06-05 | 150.00 | 8 | +---------+------------+--------+---------+ 8 rows in set (0.00 sec)
常用的聚合函数另外的用法
常用的聚合函数可以帮助我们对窗口内的数据进行汇总和计算,而不需要分组。这些函数能够以更灵活的方式处理数据,例如计算累积总和、计算移动平均等。
以下是一些常用的聚合函数在窗口函数中的应用示例:
-
SUM() OVER(): 计算指定列的累积总和。
-- 计算累积总和。 select sale_id, sale_date, amount, sum(amount) over (order by sale_date) as running_total from sales; +---------+------------+--------+---------------+ | sale_id | sale_date | amount | running_total | +---------+------------+--------+---------------+ | 1 | 2024-06-01 | 100.00 | 100.00 | | 2 | 2024-06-02 | 150.00 | 450.00 | | 3 | 2024-06-02 | 200.00 | 450.00 | | 4 | 2024-06-03 | 120.00 | 750.00 | | 5 | 2024-06-03 | 180.00 | 750.00 | | 6 | 2024-06-04 | 220.00 | 1070.00 | | 7 | 2024-06-04 | 100.00 | 1070.00 | | 8 | 2024-06-05 | 150.00 | 1220.00 | +---------+------------+--------+---------------+ 8 rows in set (0.00 sec)
-- 计算分天累积总和。 select sale_id, sale_date, amount, sum(amount) over (partition by sale_date order by sale_date) as running_total from sales order by sale_date, sale_id; +---------+------------+--------+----------------------+ | sale_id | sale_date | amount | daily_cumulative_sum | +---------+------------+--------+----------------------+ | 1 | 2024-06-01 | 100.00 | 100.00 | | 2 | 2024-06-02 | 150.00 | 150.00 | | 3 | 2024-06-02 | 200.00 | 350.00 | | 4 | 2024-06-03 | 120.00 | 120.00 | | 5 | 2024-06-03 | 180.00 | 300.00 | | 6 | 2024-06-04 | 220.00 | 220.00 | | 7 | 2024-06-04 | 100.00 | 320.00 | | 8 | 2024-06-05 | 150.00 | 150.00 | +---------+------------+--------+----------------------+ 8 rows in set (0.00 sec)
巧妙的利用
partition by
会有出色的效果 -
AVG() OVER(): 计算指定列的移动平均值。
select sale_id, sale_date, amount, avg(amount) over (order by sale_date rows between 1 preceding and current row) as moving_avg from sales; +---------+------------+--------+------------+ | sale_id | sale_date | amount | moving_avg | +---------+------------+--------+------------+ | 1 | 2024-06-01 | 100.00 | 100.000000 | | 2 | 2024-06-02 | 150.00 | 125.000000 | | 3 | 2024-06-02 | 200.00 | 175.000000 | | 4 | 2024-06-03 | 120.00 | 160.000000 | | 5 | 2024-06-03 | 180.00 | 150.000000 | | 6 | 2024-06-04 | 220.00 | 200.000000 | | 7 | 2024-06-04 | 100.00 | 160.000000 | | 8 | 2024-06-05 | 150.00 | 125.000000 | +---------+------------+--------+------------+ 8 rows in set (0.00 sec)
这里计算每笔销售及其前一笔销售的平均销售额,这被称为移动平均值。