找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

676

积分

0

好友

86

主题
发表于 5 天前 | 查看: 22| 回复: 0

很多人第一次接触 OVER 窗口函数,都是为了解决这类典型的数据分析需求:

  • 计算每个月的累计用户数。
  • 统计截止当月的累计收入。
  • 生成按时间滚动的各项指标。

SQL 语句写是写出来了,可一旦执行结果和预期不符,调试过程往往让人怀疑人生。其实,问题通常不在于 SQL 语法技巧本身,而在于一个更根本的困惑:没有真正理解 OVER 到底是在对“哪一批”数据进行计算。

本文将通过一套极简、脱敏、专为讲解而设计SQL 示例,帮你把 OVER 窗口函数的计算逻辑彻底捋清楚。

一、一个精心设计的简化场景

假设我们有一张订单表 order_table,它记录了每天产生的订单金额:

order_table

字段名 含义
order_date 订单日期
order_amount 订单金额

我们的分析目标是:

统计每个月的订单总额,并同时计算出“截至当月月底”的累计订单总金额。

请特别注意这个关键词:截至当月月底(累计)。这意味着计算需要带上历史数据。

二、第一步:先算清“每个月的新增额”(不使用 OVER)

在进行累计计算之前,我们得先把基础数据——每个月的独立销售额——汇总清楚。

SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month_time,
    SUM(order_amount) AS month_amount
FROM order_table
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month_time;

这一步的运算非常纯粹:

  • 不涉及任何历史数据。
  • 不进行任何累计计算。
  • 仅仅是把“散落在每一天的数据”按月份聚合,压缩成“每个月一行”的汇总结果。

查询结果大致如下:

month_time month_amount
2025-01 10000
2025-02 15000
2025-03 12000

三、关键问题:如何得到“截至当月的累计值”?

面对这个需求,很多人的第一反应是:能不能在 GROUP BY 分组的时候,“顺手”把累计值也算出来?

答案是:不行。

因为 GROUP BY 的核心职责只有一个:把多行数据根据分组键聚合成一行。一旦聚合完成,明细行就消失了。

而“累计”这件事的本质是:

站在当前这一行结果的位置,回过头去“看”历史上所有符合条件的数据行。

这种“既需要保留明细行,又需要基于某种顺序访问其他行数据”的能力,正是 窗口函数(Window Function) 存在的意义。

四、OVER 登场:定义你的数据“窗口”

现在,我们把上一步得到的月度汇总数据当作一个子查询,然后在其之上应用 OVER 来进行累计计算。

SELECT
    month_time,
    month_amount,
    SUM(month_amount) OVER (
        ORDER BY month_time
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS total_amount_at_month_end
FROM (
    SELECT
        DATE_FORMAT(order_date, '%Y-%m') AS month_time,
        SUM(order_amount) AS month_amount
    FROM order_table
    GROUP BY DATE_FORMAT(order_date, '%Y-%m')
) t
ORDER BY month_time;

这条 SQL 是理解 OVER 逻辑的最佳切入点。我们来逐部分拆解。

五、逐句拆解:OVER 到底在干什么?

1️⃣ SUM(month_amount)

这部分并非重点,它只是一个普通的聚合函数(如 SUM, AVG, COUNT 等)。真正的魔法在于后面的 OVER() 子句。

2️⃣ ORDER BY month_time

这一句定义了累计的“方向”或“顺序”。
它告诉数据库:“请按照 month_time 从早到晚的顺序来建立累计的逻辑。” 如果没有这个排序,就谈不上“累计”概念。

3️⃣ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

这是整个 OVER 子句的灵魂,决定了“窗口”的范围。

直译过来是:“从(分区的)第一行开始,一直计算到当前行。”
换成业务语言就是:历史所有月份 + 当前月份

六、用“人脑”模拟窗口的滑动过程

假设我们得到的第一步月度数据如下:

month_time month_amount
2025-01 10000
2025-02 15000
2025-03 12000

那么,窗口函数在处理每一行时,其“看到”的数据窗口和计算结果分别是:

  • 处理 2025-01 这一行时
    • 数据窗口 = [2025-01]
    • 累计金额 = 10000
  • 处理 2025-02 这一行时
    • 数据窗口 = [2025-01 → 2025-02]
    • 累计金额 = 10000 + 15000 = 25000
  • 处理 2025-03 这一行时
    • 数据窗口 = [2025-01 → 2025-03]
    • 累计金额 = 10000 + 15000 + 12000 = 37000

这个过程完美诠释了“滑动窗口”的直观含义:窗口随着当前处理行的移动,在有序的数据集上向后滑动,并重新划定计算范围。

七、OVER 与 GROUP BY 的核心区别

很多人容易混淆这两者,下表给出了清晰的对比:

维度 GROUP BY OVER (窗口函数)
是否合并输出行 是(多行变一行) 否(保持原行数)
是否保留明细数据
是否能进行累计、排名等计算
计算是否依赖行间顺序

一句话总结:

GROUP BY 改变了查询结果集的结构(行数减少);而 OVER 不改变结果集结构,它只改变每一行数据在进行计算时所“看到”的数据范围。

八、一个实用变体:实现滚动统计

理解了窗口范围的定义后,我们可以轻松实现各种变体。例如,将窗口范围改为:

ROWS BETWEEN 2 PRECEDING AND CURRENT ROW

其含义立刻变为:“从当前行往前数2行,直到当前行”,也就是 近3行 的数据。
在按月排序的上下文中,这就实现了“近3个月的滚动销售总额”的计算。

看,SQL的主体结构完全没变,我们仅仅通过修改 OVER() 子句中的窗口范围定义,就实现了截然不同的业务逻辑。这正是窗口函数强大且优雅的地方,也是它被称为“分析型SQL”利器的原因。

九、最终要记住的三个要点

关于 OVER 窗口函数,你只需要深刻理解这三句话:

  1. 它不是用来分组聚合的,而是用来定义数据观察“视角”或“窗口”的。
  2. ORDER BY 决定了计算进行的逻辑或时间方向(从哪到哪)。
  3. ROWS BETWEEN ... 则精确划定了这个“窗口”的大小,即你能看到多远(多少行)的历史或未来数据。

当你真正在脑中建立起“滑动窗口”的动态画面时,你会发现,无论是累计求和 (SUM)、排名 (RANKROW_NUMBER),还是计算同比环比,本质上都是同一套窗口机制在不同场景下的应用。

希望这个从零拆解的示例能帮助你彻底掌握 SQL 窗口函数的精髓。如果你想深入探索更多数据库高级特性或实践案例,欢迎在 云栈社区 与更多开发者交流学习。




上一篇:开源本地音视频处理 WebUI:Voice-Pro v3.2.0 核心功能与部署指南
下一篇:零刻ME Pro模块化NAS评测:可换主板设计搭配飞牛fnOS实战体验
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 02:49 , Processed in 0.421762 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表