GROUP BY+SUM+OVER+ORDER BY工作原理及MySQL文档疑问解析
基础案例分析
先看这段SQL及其运行结果:
SELECT number , SUM(number) OVER (ORDER BY number) cumulative_number FROM (SELECT 1 number UNION SELECT 2 UNION SELECT 3) inside GROUP BY number
运行结果:
| number | cumulative_number |
|---|---|
| 1 | 1 |
| 2 | 3 |
| 3 | 6 |
我能理解这段SQL生成数值及累计统计结果,也大致知道运行逻辑:GROUP BY number生成了number=1、2、3的分组,但SUM() OVER在处理number=2时能访问分组外的行,这是由窗口定义中的ORDER BY number指定的。
但我无法理解MySQL文档描述与实际的关联:
order_clause: ORDER BY子句指定如何排序每个分区内的行。ORDER BY中相等的行视为对等体。若省略ORDER BY,分区行无序,无处理顺序,所有行均为对等体。
从数学定义来说,分区(分组)是无序集合,对分区内的行排序难以理解;而且SUM运算本身不依赖操作数顺序,这就更困惑了。
延伸案例疑问
再看另一段SQL:
SELECT ROUND(number, -1) tens , SUM(COUNT(*)) OVER (ORDER BY number) cumulative_number FROM (SELECT 1 number UNION SELECT 2 UNION SELECT 11 UNION SELECT 12) inside GROUP BY tens
这里的疑问是:11和12不相等,ORDER BY number能区分它们,但二者同属tens=10分组。按文档描述,SUM() OVER似乎需要选择该分组内11或12对应的累计和3或4,但实际结果是4,为什么会选这个结果?
同时,到底我没领会MySQL文档中的哪些核心要点,还是文档存在描述不足?
核心要点解析
1. 窗口分区与GROUP BY分组的本质区别
首先要明确:窗口函数的PARTITION BY和GROUP BY是完全独立的概念。你写的SQL里没有指定PARTITION BY,所以整个聚合后的结果集就是一个大的窗口分区,而GROUP BY只是先做了分组聚合,生成聚合后的行,之后窗口函数是基于这些聚合后的行来计算的。你之前误解了GROUP BY生成的分组就是窗口分区,这是关键错误。
2. ORDER BY在窗口聚合中的隐性作用
当窗口函数中使用ORDER BY但没指定窗口帧(FRAME)时,MySQL会默认使用RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW作为窗口帧。这意味着:
- 对每一行,聚合计算会包含从窗口分区第一行到当前行的所有行
- 这就是第一个案例中累计和的来源:第一行仅计算自身,第二行计算前一行加自身,第三行计算前两行加自身
文档里说的“排序每个分区内的行”,这里的分区是窗口分区(不是GROUP BY的分组),排序后确定了窗口帧的范围——当前行及之前的所有行,这才是累计计算的核心。
3. 延伸案例的结果解释
在第二个案例中,执行顺序是:
- 子查询生成原始行:1、2、11、12
GROUP BY tens聚合后得到两行:tens=0(对应原始行1、2,COUNT(*)=2),tens=10(对应原始行11、12,COUNT(*)=2)- 窗口函数
SUM(COUNT(*)) OVER (ORDER BY number)基于聚合后的两行计算:- 聚合后的行按
number排序,tens=0的行在前,tens=10的行在后 - 应用默认窗口帧后,第一行仅计算自身的
COUNT(*),结果为2;第二行计算前一行加自身的COUNT(*),结果为2+2=4
- 聚合后的行按
你之前误以为窗口函数会考虑原始行的11和12,但实际上GROUP BY已经把它们合并成一行了,窗口函数处理的是聚合后的两行,所以最终结果是4。
4. 文档描述的关键点与不足
文档里的“分区内的行”指的是窗口分区的行,不是GROUP BY的分组。你之前混淆了这两个概念,这是理解的核心障碍。
另外,文档没有明确说明:当窗口聚合使用ORDER BY但未指定窗口帧时,默认使用RANGE UNBOUNDED PRECEDING TO CURRENT ROW——这是SQL标准的默认行为,但文档的缺失容易导致误解。
内容的提问来源于stack exchange,提问作者William Entriken

