开窗函数定义
一组行被称为一个窗口,开窗函数是指可以用于「分区」或「分组」计算的函数。这些函数结合 OVER 子句对组内的数据进行编号,并进行求和、计算平均值等统计。从这个角度来说,SUM、AVG 以及 ROW_NUMBER 等都可以称为开窗函数。
开窗函数可以分别应用于每个分区,把每个分区看成是一个窗口,并未每个分区进行计算。开窗函数必须放在 OVER 子句前边。
SQL Server 早古版本就提供了对开窗函数的支持,但 MySQL 直到 8.0 版本才开始支持,所以部分只使用老版本 MySQL 的工程师知识面会有欠缺。推荐使用 dbngin 管理多个数据库环境。
1. 将 OVER 子句与聚合函数结合使用
OVER ([PARTITION BY vale_expression, ...[n]])
- PARTITION BY:将结果集划分为多个分区。开窗函数分别应用于每个分区,并为每个分区计算函数值。
- vale_expression:指定对行集进行分区所依据的列,该列必须是 FROM 子句中生成的列,而且不能引用选择列表中的表达式或别名。
可以在单个查询中使用多个开窗函数,每个函数的 OVER 子句在分区和排序上可以不同。
例1
设有课程表 Course(Cno, CName, Credit, Semester)。查询全部课程的课程号、课程名、开课学期、学分以及该学期开设课程的总分、平均分、最低分、最高分。
SELECT
Cno,
CName,
Semester,
Credit,
SUM( credit ) OVER ( PARTITION BY Semester ) AS 'Total',
AVG( credit ) OVER ( PARTITION BY Semester ) AS 'Avg',
MIN( credit ) OVER ( PARTITION BY Semester ) AS 'Min',
MAX( credit ) OVER ( PARTITION BY Semester ) AS 'Max'
FROM
Course
观察结果,最简单情况下,开窗函数可以理解为,将整张表的,分区聚合函数查询的结果,冗余到当前表的每一行记录中。
例2
订单订单明细表 OrderDetail
CREATE TABLE OrderDetail(
OrderID int NOT NULL, --- 订单号
ProductID int NOT NULL, --- 产品号
OrderQty int NOT NULL --- 订购数量
)
现在查询订单号、产品号、订购数量、每个订单的总订购数量以及每个产品的订购数量占该订单总订购数量的百分比,百分比保留到小数点后 2 位。
分析: 先按照订单号进行分区,然后对每个订单号进行分区,对分区中的订购数量进行求和即可。
SUM(OrderQty) OVER (PARTITION BY OrderID)
第二层查询要求统计每个产品订购数量占订单总订购数量的百分比,可以用产品数量除以订单总订购量得到,然后再进行一下小数点的转化即可。
SELECT
OrderID 订单号,
ProductID 产品号,
OrderQty 订购数量,
SUM( OrderQty ) OVER ( PARTITION BY OrderID ) AS 总计,
CAST(
1.0 * OrderQty / SUM( OrderQty ) OVER ( PARTITION BY OrderID ) * 100 AS DECIMAL ( 5, 2 )) AS 所占百分比
FROM
OrderDetail
2. 将 OVER 子句与排名函数一起使用
排名函数为分区中的每一行返回一个排名值。更具所用函数不同,某些行可能与其他行具有相同的排名值(并列第一)。排名函数具有不确定性。
(1)RANK() 函数
RANK() OVER ([<partition_by_clause>, ...[n]] <order_by_clause>)
- <partition_by_clause>:将 FROM 子句生成的结果集划分成排名函数适用的分区。
- <order_by_clause>:分区中的排序依据列。
RANK() 函数每个分区的排名从 1 开始,如果排序时有排序值相同的行,则相同的行具有相同的排名,因此不一定返回连续的整数。
例3
用例 2 的 OrderDetail 表,查询订单号、产品号、订购数量以及每个产品在每个订单中的订购数量排名。
SELECT
OrderID,
OrderQty,
ProductID,
RANK() OVER ( PARTITION BY OrderID ORDER BY OrderQty DESC ) AS 'RANK'
FROM
OrderDetail
ORDER BY
OrderID
(2)DENSE_RANK() 函数
唯一的区别是排名中间没有任何间断,返回一个连续的整数。
(3)NTILE() 函数
作用是将有序分区中的行划分到指定数目的数组中,每个组有一个编号,编号从 1 开始。对于每一行,函数将返回此行所属的组的编号。
NTILE(integer_expression) OVER ([<partition_by_clause>] <order_by_clause>)
例4
用例 2 的 OrderDetail 表。将该表数据按订购数量降序排序,并将该表数据划分到 4 个组中。
SELECT
OrderID,
ProductID,
OrderQty,
NTILE( 4 ) OVER ( ORDER BY OrderQty DESC ) AS 'FourGroups'
FROM
OrderDetail
全部数据分为 4 组,因此不需要分区,直接在 OVER 子句中排序即可
(4)ROW_NUMBER() 函数
返回结果集中每个分区内行的序列号,每个分区的第一行从 1 开始。
ROW_NUMBER() OVER ([<partition_by_clause>] <order_by_clause>)
例5
查询「电冰箱」类商品的商品名、销售单价以及该商品在该类商品中的价格排名。
自己实现
例6
查询 2022 年 1 月 1 日从每个供应商采购商品情况,列出商品号、商品名、供应商编号、进货数量,以及每个商品的进货数量在该供应商的所有商品的进货数量中的排名。
自己实现