窗口函数基础:OVER 与分区
问题:为每行添加聚合信息
假设想在订单列表中显示每个订单的金额和总平均金额:
sql
-- 尝试 1:只显示平均值(1 行)
SELECT AVG(amount) FROM orders;
-- 尝试 2:使用子查询(可行但笨拙)
SELECT product, amount, (SELECT AVG(amount) FROM orders) AS avg_amount
FROM orders;更好的方法:窗口函数。
第一个窗口函数
sql
SELECT
product,
amount,
AVG(amount) OVER () AS avg_amount
FROM orders;结果:
text
product | amount | avg_amount
------------+---------+------------
Laptop | 1200.00 | 331.000000
Mouse | 25.00 | 331.000000
Keyboard | 80.00 | 331.000000
Monitor | 300.00 | 331.000000
Headphones | 50.00 | 331.000000关键点:
- 返回 5 行(与原表相同)
- 每行都有
avg_amount列 OVER ()定义了窗口(这里是整个表)
OVER 子句
OVER 将聚合函数转换为窗口函数。
基本语法:
sql
function() OVER (
[PARTITION BY column]
[ORDER BY column]
[frame_clause]
)空 OVER:整个表作为窗口
sql
SELECT
product,
amount,
COUNT(*) OVER () AS total_orders,
SUM(amount) OVER () AS total_sales
FROM orders;结果:
text
product | amount | total_orders | total_sales
------------+---------+--------------+-------------
Laptop | 1200.00 | 5 | 1655.00
Mouse | 25.00 | 5 | 1655.00
Keyboard | 80.00 | 5 | 1655.00
Monitor | 300.00 | 5 | 1655.00
Headphones | 50.00 | 5 | 1655.00PARTITION BY:创建窗口分区
PARTITION BY 将数据分成多个窗口,每个窗口独立计算。
sql
-- 扩展 orders 表,添加用户信息
SELECT
u.name,
o.product,
o.amount,
SUM(o.amount) OVER (PARTITION BY o.user_id) AS user_total
FROM orders o
JOIN users u ON o.user_id = u.id;结果:
text
name | product | amount | user_total
---------+------------+---------+------------
Alice | Laptop | 1200.00 | 1280.00
Alice | Keyboard | 80.00 | 1280.00
Bob | Mouse | 25.00 | 75.00
Bob | Headphones | 50.00 | 75.00
Charlie | Monitor | 300.00 | 300.00工作原理:
PARTITION BY user_id按用户分窗口- Alice 的两行在一个窗口,计算和为 1280
- Bob 的两行在一个窗口,计算和为 75
- Charlie 的一行单独一个窗口,和为 300
ORDER BY:窗口内排序
ORDER BY 定义窗口内的行顺序,影响累计计算。
sql
SELECT
u.name,
o.order_date,
o.amount,
SUM(o.amount) OVER (
PARTITION BY o.user_id
ORDER BY o.order_date
) AS running_total
FROM orders o
JOIN users u ON o.user_id = u.id
ORDER BY u.name, o.order_date;结果:
text
name | order_date | amount | running_total
---------+------------+---------+---------------
Alice | 2026-01-15 | 1200.00 | 1200.00
Alice | 2026-01-17 | 80.00 | 1280.00
Bob | 2026-01-16 | 25.00 | 25.00
Bob | 2026-01-19 | 50.00 | 75.00
Charlie | 2026-01-18 | 300.00 | 300.00累计求和:
- Alice 第一行:1200
- Alice 第二行:1200 + 80 = 1280
- Bob 第一行:25
- Bob 第二行:25 + 50 = 75
窗口函数 vs 聚合函数
| 特性 | 聚合函数 | 窗口函数 |
|---|---|---|
| 返回行数 | 合并为少数行 | 保持原行数 |
| 需要 GROUP BY | 是(非聚合列) | 否 |
| 可访问其他行 | 否 | 是 |
| 用途 | 汇总统计 | 行级分析 |
示例对比:
sql
-- 聚合:每个用户一行
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id;
-- 结果:3 行
-- 窗口:每个订单一行
SELECT user_id, product, amount,
SUM(amount) OVER (PARTITION BY user_id) AS user_total
FROM orders;
-- 结果:5 行常用窗口函数
聚合窗口函数
所有聚合函数都可以作为窗口函数:
SUM() OVER ():求和AVG() OVER ():平均值COUNT() OVER ():计数MIN() OVER ():最小值MAX() OVER ():最大值
排名函数(下一节详细讲解)
ROW_NUMBER() OVER ():行号RANK() OVER ():排名(有并列)DENSE_RANK() OVER ():密集排名
值函数
LAG() OVER ():前一行的值LEAD() OVER ():后一行的值FIRST_VALUE() OVER ():窗口第一行LAST_VALUE() OVER ():窗口最后一行
实际应用示例
示例 1:计算占比
sql
SELECT
product,
amount,
ROUND(amount / SUM(amount) OVER () * 100, 2) AS percentage
FROM orders;结果:
text
product | amount | percentage
------------+---------+------------
Laptop | 1200.00 | 72.51
Mouse | 25.00 | 1.51
Keyboard | 80.00 | 4.83
Monitor | 300.00 | 18.13
Headphones | 50.00 | 3.02示例 2:与平均值比较
sql
SELECT
product,
amount,
AVG(amount) OVER () AS avg_amount,
amount - AVG(amount) OVER () AS diff
FROM orders;示例 3:分组内排名
sql
SELECT
u.name,
o.product,
o.amount,
RANK() OVER (PARTITION BY o.user_id ORDER BY o.amount DESC) AS rank_in_user
FROM orders o
JOIN users u ON o.user_id = u.id;MySQL 8.0 窗口函数支持
MySQL 8.0+ 支持窗口函数,语法与 PostgreSQL 相同。
MySQL 5.7 及更早版本不支持窗口函数,需要用子查询模拟(复杂且低效)。
练习
基本窗口:查询每个订单及其占总销售额的百分比。
分区窗口:查询每个用户的订单,显示该用户的总消费和平均订单金额。
累计求和:按日期顺序显示订单,计算累计销售额。
分组累计:按用户分组,计算每个用户的累计消费。
多窗口:同时显示每个订单的用户总消费和全局总消费。
与平均值对比:查询金额高于用户平均订单金额的订单。
最大最小值:查询每个用户的最高和最低订单金额(使用窗口函数)。
实际场景:创建一个销售仪表板查询,显示产品、金额、占比、累计占比。
完成练习后,打开本章答案页核对。
下一步
学会了窗口函数基础。下一节学习排名函数:ROW_NUMBER、RANK、DENSE_RANK 的使用和区别。