📊 SQL 入门 Day 14:聚合窗口函数 — 让 SUM / AVG 也能"滑动"起来

场景引入

前面两天我们学了窗口函数的基础语法和排名类窗口函数。但窗口函数真正的威力,在于它能把聚合函数 (SUM、AVG、COUNT、MAX、MIN)和窗口的"滑动"能力结合起来------保留每一行明细的同时,还能看到累计、移动平均、分组占比

普通聚合函数会让多行变一行(GROUP BY),而聚合窗口函数让每一行都保留,同时看到聚合结果。


一、聚合窗口函数基础语法

sql 复制代码
<聚合函数>(<表达式>) OVER (
    [PARTITION BY <分组列>]
    [ORDER BY <排序列>]
    [<窗口帧子句>]
)

与普通聚合函数唯一的区别就是多了 OVER (...)

常用聚合函数

函数 含义
SUM() 求和
AVG() 平均值
COUNT() 计数
MAX() 最大值
MIN() 最小值

二、不加 ORDER BY:每组总览

OVER() 中只有 PARTITION BY 而没有 ORDER BY 时,窗口就是整个分区,相当于把 GROUP BY 的结果贴回每一行。

示例:各部门总薪资

sql 复制代码
SELECT
    name,
    department,
    salary,
    SUM(salary) OVER (PARTITION BY department) AS dept_total,
    AVG(salary) OVER (PARTITION BY department) AS dept_avg
FROM employees;

执行结果:

name department salary dept_total dept_avg
张三 技术部 25000 75000 25000.0
李四 技术部 28000 75000 25000.0
王五 技术部 22000 75000 25000.0
赵六 市场部 18000 35000 17500.0
钱七 市场部 17000 35000 17500.0

关键点:

  • 每一行都显示所在部门的薪资总和与平均值
  • 可以用 dept_total / dept_avg 计算该员工薪资在部门中的相对位置
  • ✅ 这种写法比 GROUP BY 后再 JOIN 回原表优雅得多

三、加 ORDER BY:累计与滚动

当加上 ORDER BY 后,窗口默认从分区第一行到当前行 ,这就实现了累计(Running Total)

默认窗口帧:RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

示例:按日期累计销售额

sql 复制代码
SELECT
    sale_date,
    amount,
    SUM(amount) OVER (ORDER BY sale_date) AS running_total
FROM sales
ORDER BY sale_date;

执行结果:

sale_date amount running_total
2026-01-01 1200 1200
2026-01-02 800 2000
2026-01-03 1500 3500
2026-01-05 900 4400
2026-01-06 2000 6400

注意事项:

  • 2026-01-03 到 2026-01-05 之间有间隔(1月4日无数据)
  • 默认帧是 RANGE BETWEEN,当日期不连续时,会把相同日期的行算在一起
  • 如果要精确控制行数,需要显式指定 ROWS BETWEEN

四、窗口帧(Frame)详解

窗口帧定义了「窗口有多大」,三种模式:

模式 含义 适用场景
ROWS 按物理行数 移动平均、精确行数控制
RANGE 按逻辑值范围(ORDER BY 列的值) 日期连续累计
GROUPS 按 ORDER BY 值相同的组 较少使用

常用帧边界

sql 复制代码
-- 从分区开始到当前行(累计)
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

-- 当前行前后各2行(共5行)
ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING

-- 从第1行到第N行
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING

-- 仅当前行
ROWS BETWEEN CURRENT ROW AND CURRENT ROW

五、实战:3 日移动平均

sql 复制代码
SELECT
    sale_date,
    amount,
    AVG(amount) OVER (
        ORDER BY sale_date
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3d
FROM sales
ORDER BY sale_date;

执行结果:

sale_date amount moving_avg_3d
2026-01-01 1200 1200.0
2026-01-02 800 1000.0
2026-01-03 1500 1166.7
2026-01-05 900 1066.7
2026-01-06 2000 1466.7

解释:

  • 第1行不够3行 → 只算1行平均值
  • 第2行不够3行 → 算前2行平均值
  • 第3行之后 → 算前3行平均值

六、按部门计算移动平均

sql 复制代码
SELECT
    name,
    department,
    salary,
    AVG(salary) OVER (
        PARTITION BY department
        ORDER BY salary
        ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
    ) AS local_avg
FROM employees
ORDER BY department, salary;

这个查询会在每个部门内部 ,对每行取其前一行 + 当前行 + 后一行的薪资平均值。

应用场景:

  • 薪资异常检测:如果某人的薪资远低于 local_avg 或远高于 local_avg
  • 趋势平滑:去除极端值的影响

七、COUNT + 窗口:分组内的行号之外的统计

sql 复制代码
SELECT
    department,
    name,
    salary,
    COUNT(*) OVER (PARTITION BY department) AS dept_headcount,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_in_dept
FROM employees;

用途: 同时看到部门人数 + 部门内排名,一个查询搞定!


八、MAX / MIN 窗口:找出组内极值并做差值

sql 复制代码
SELECT
    department,
    name,
    salary,
    MAX(salary) OVER (PARTITION BY department) AS dept_max,
    MAX(salary) OVER (PARTITION BY department) - salary AS gap_to_max
FROM employees;

结果:

department name salary dept_max gap_to_max
技术部 李四 28000 28000 0
技术部 张三 25000 28000 3000
技术部 王五 22000 28000 6000
市场部 赵六 18000 18000 0
市场部 钱七 17000 18000 1000

九、综合实战:销售月报分析

sql 复制代码
SELECT
    DATE_FORMAT(sale_date, '%Y-%m') AS month,
    SUM(amount) AS monthly_total,
    AVG(SUM(amount)) OVER (
        ORDER BY DATE_FORMAT(sale_date, '%Y-%m')
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS quarterly_avg,
    SUM(SUM(amount)) OVER (
        ORDER BY DATE_FORMAT(sale_date, '%Y-%m')
    ) AS ytd_total
FROM sales
GROUP BY DATE_FORMAT(sale_date, '%Y-%m')
ORDER BY month;

这里用了嵌套聚合:先 GROUP BY 算出每月总额,再用窗口函数计算季度平均和年度累计。


⚠️ 注意事项

  1. ROWS vs RANGE 默认行为不同

    • ORDER BY x 不加帧子句 → 默认 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    • RANGE 会把 ORDER BY 值相同的行全部纳入窗口,可能导致意外结果
  2. ORDER BY 列有重复值时注意 RANGE

    sql 复制代码
    -- 如果 sale_date 有重复,RANGE 会把所有相同日期的行都算上
    SUM(amount) OVER (ORDER BY sale_date)

    改成 ROWS 可精确控制行数

  3. 性能考虑

    • 窗口函数通常比同功能的子查询快,因为只需扫描一次表
    • PARTITION BY 列有无索引会影响性能
  4. 窗口帧不能反向指定

    • ROWS BETWEEN 1 FOLLOWING AND 2 PRECEDING ❌ 会报错
    • 起始边界必须在终止边界之前
  5. 所有的聚合窗口函数都可以搭配 FILTER

    sql 复制代码
    SUM(salary) FILTER (WHERE department = '技术部')
        OVER (ORDER BY hire_date)

    但注意:FILTER 不是所有数据库都支持(PostgreSQL ✅,MySQL ❌)


总结

场景 写法 用途
分组总览 SUM(x) OVER (PARTITION BY dept) 保留明细+分组统计
累计求和 SUM(x) OVER (ORDER BY date) 运行总量
移动平均 AVG(x) OVER (ORDER BY d ROWS BETWEEN N PRECEDING AND CURRENT ROW) 平滑趋势
组内比较 MAX(x) OVER (PARTITION BY dept) - x 找出差值
占比计算 x / SUM(x) OVER (PARTITION BY dept) 分组占比

Day 14 的核心心法是:聚合窗口函数 = 普通聚合函数 + OVER(),让聚合结果和每一行共存。这是数据分析中最常用的 SQL 技巧之一,掌握它,你的 SQL 水平会上一个台阶。

相关推荐
自进化Agent智能体1 小时前
Hermes CLI 界面详解——命令行交互全攻略
后端
1点东西2 小时前
做了个AI日志平台,零代码接入,嘎嘎好用
后端·aigc·自动化运维
程序员AI工坊2 小时前
Agent 开发:ReAct 循环与工具调用实战——从单次调用到自主 Agent
人工智能·后端·python·langchain·agent·react
不爱说话郭德纲3 小时前
我只给了 TRAE Work 一张差评截图,它最后却把自己的 P0 结论推翻了?
前端·后端·架构
tech讯息3 小时前
企业做视频生成,应该选择哪些云上多模态模型平台,以平衡模型质量、可控性和推理成本?AWS 分层选型路径
后端
潇客3 小时前
前端第一次写 Node 服务:一个请求到底经历了什么?
后端
后除3 小时前
Debian 安装与使用 Certbot
后端·nginx·https
ClouGence3 小时前
CloudDM:开源免费!一站式数据库访问、SQL审核、权限与脱敏管控平台
数据库·sql·开源
小林敲代码77883 小时前
Spring Boot 3 升级踩坑:aj-captcha 行为验证码底图加载失效
java·spring boot·后端
雪碧5133 小时前
基于afsim的训练多个智能体算法控制(持续关注和收藏后续会同步整个源码包)
后端