SQL窗口函数中ORDER BY对SUM()的影响解析
在SQL窗口函数中,ORDER BY子句会改变SUM()的行为。
没有ORDER BY时,SUM()计算整个分组的固定总和(如部门总薪资8750);
添加ORDER BY后,SUM()变为按指定顺序的累计求和(如2450→7450→8750)。
这种差异源于窗口范围的改变:无ORDER BY时窗口是整个分组,有ORDER BY时窗口默认从第一行到当前行。
其他聚合函数如AVG、COUNT等也遵循相同逻辑。
该设计体现了SQL对数据顺序处理的灵活性,使分析函数能同时支持整体统计和滚动计算两种模式。
为什么 ORDER BY 能让 SUM() 变成累计?
一、核心原理
sql
-- 没有 ORDER BY:返回分组总和
SUM(SAL) OVER (PARTITION BY DEPTNO)
-- 结果:每个部门所有行的值都一样(部门总薪资)
-- 有 ORDER BY:返回累计值
SUM(SAL) OVER (PARTITION BY DEPTNO ORDER BY HIREDATE)
-- 结果:从第一行到当前行的累加
二、底层执行逻辑对比
场景:10号部门员工入职情况
| 员工 | 入职日期 | 薪资 |
|---|---|---|
| CLARK | 1981-06-09 | 2450 |
| KING | 1981-11-17 | 5000 |
| MILLER | 1982-01-23 | 1300 |
❌ 没有 ORDER BY(分组总和)
sql
SUM(SAL) OVER (PARTITION BY DEPTNO)
执行过程:
text
步骤1:扫描整个10号部门,计算总薪资 = 2450 + 5000 + 1300 = 8750
步骤2:把 8750 填入每一行
结果:
| 员工 | 薪资 | SUM() |
|---|---|---|
| CLARK | 2450 | 8750 |
| KING | 5000 | 8750 |
| MILLER | 1300 | 8750 |
相当于:先算完总和,再广播到每一行
✅ 有 ORDER BY(累计求和)
sql
SUM(SAL) OVER (PARTITION BY DEPTNO ORDER BY HIREDATE)
执行过程:
text
步骤1:按 HIREDATE 排序 → CLARK(6月) → KING(11月) → MILLER(1月)
步骤2:逐行计算累加
- 第1行(CLARK):累加 = 2450
- 第2行(KING):累加 = 2450 + 5000 = 7450
- 第3行(MILLER):累加 = 2450 + 5000 + 1300 = 8750
结果:
| 员工 | 入职日期 | 薪资 | SUM() |
|---|---|---|---|
| CLARK | 1981-06-09 | 2450 | 2450 |
| KING | 1981-11-17 | 5000 | 7450 |
| MILLER | 1982-01-23 | 1300 | 8750 |
相当于:按顺序维护一个"运行总数"
三、图解对比
text
没有 ORDER BY(分组总和):
┌─────────────────────────────────────┐
│ 计算总薪资 = 8750 │
│ ↓ │
│ ┌────────┐ │
│ │ CLARK │ ← 8750 │
│ │ KING │ ← 8750 │
│ │ MILLER │ ← 8750 │
│ └────────┘ │
└─────────────────────────────────────┘
有 ORDER BY(累计求和):
┌─────────────────────────────────────┐
│ 按日期排序后逐行累加: │
│ ┌────────┐ │
│ │ CLARK │ ← 2450 (只有自己) │
│ │ KING │ ← 2450+5000=7450 │
│ │ MILLER │ ← 2450+5000+1300=8750 │
│ └────────┘ │
└─────────────────────────────────────┘
四、所有聚合窗口函数的行为
| 函数 | 无 ORDER BY | 有 ORDER BY |
|---|---|---|
SUM() |
分组总和 | 累计求和 |
AVG() |
分组平均 | 累计平均 |
COUNT() |
分组总数 | 累计计数 |
MAX() |
分组最大值 | 累计最大值(到当前行为止的最大值) |
MIN() |
分组最小值 | 累计最小值(到当前行为止的最小值) |
示例:累计最大值
sql
SELECT
ENAME,
HIREDATE,
SAL,
MAX(SAL) OVER (ORDER BY HIREDATE) AS 截止当前的最高薪资
FROM EMP;
结果:
| 姓名 | 入职日期 | 薪资 | 截止当前最高 |
|---|---|---|---|
| SMITH | 1980-12-17 | 800 | 800 |
| JONES | 1981-04-02 | 2975 | 2975 |
| BLAKE | 1981-05-01 | 2850 | 2975 |
| KING | 1981-11-17 | 5000 | 5000 |
五、为什么这样设计?
设计哲学
| 特性 | 说明 |
|---|---|
| 无 ORDER BY | 表示对整个"窗口"(分组)做聚合,不关心顺序 |
| 有 ORDER BY | 给窗口赋予了"顺序"概念,聚合变成"滚动计算" |
类比 Excel
text
无 ORDER BY = 选中整列,看总和
有 ORDER BY = 从第一行开始,每行显示到当前行的累计
六、完整语法理解
sql
SUM(SAL) OVER (
PARTITION BY DEPTNO -- 1. 定义窗口边界(按部门分组)
ORDER BY HIREDATE -- 2. 定义窗口内顺序(按日期排序)
)
-- 默认窗口范围:ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
-- 即:从分组第一行 → 当前行
等价完整写法
sql
SUM(SAL) OVER (
PARTITION BY DEPTNO
ORDER BY HIREDATE
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -- 默认行为
)
七、记忆口诀
没有排序,全员一样
有了排序,累加递进
日期顺序,决定累加
从前往后,逐行滚动
八、快速自测
sql
-- 问:下面两句话的区别是什么?
-- Q1: SUM(SAL) OVER (PARTITION BY DEPTNO)
-- Q2: SUM(SAL) OVER (PARTITION BY DEPTNO ORDER BY HIREDATE)
-- 答:
-- Q1:返回每个部门的薪资总和(每行相同)
-- Q2:返回按入职时间累计的薪资(逐行递增)
💡 总结一句话
ORDER BY 告诉数据库"按照什么顺序累加",没有 ORDER BY 就没有顺序,只能返回固定总和。