Skip to content

窗口函数基础: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.00

PARTITION 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 及更早版本不支持窗口函数,需要用子查询模拟(复杂且低效)。

练习 ​

  1. 基本窗口:查询每个订单及其占总销售额的百分比。

  2. 分区窗口:查询每个用户的订单,显示该用户的总消费和平均订单金额。

  3. 累计求和:按日期顺序显示订单,计算累计销售额。

  4. 分组累计:按用户分组,计算每个用户的累计消费。

  5. 多窗口:同时显示每个订单的用户总消费和全局总消费。

  6. 与平均值对比:查询金额高于用户平均订单金额的订单。

  7. 最大最小值:查询每个用户的最高和最低订单金额(使用窗口函数)。

  8. 实际场景:创建一个销售仪表板查询,显示产品、金额、占比、累计占比。

完成练习后,打开本章答案页核对。

下一步 ​

学会了窗口函数基础。下一节学习排名函数:ROW_NUMBER、RANK、DENSE_RANK 的使用和区别。

回到第四章概览 · 返回SQL 学习路线

用心记录每一步成长