SUM OVER是窗口函数,每行输出对应输入行,不压缩行数;普通SUM是聚合函数,需配合GROUP BY,结果行数≤原表,丢失明细。

什么是SUM OVER,它和普通SUM有什么区别
SUM OVER 是窗口函数,不是聚合函数。它不会压缩行数,而是对每行计算一个“当前行所在窗口范围内的累计和”。这正是计算实时预算消耗率的关键——你需要在不丢失明细行的前提下,拿到部门级的总预算、总已用金额。
普通 SUM() 一用就 GROUP BY,结果只剩一行/部门,没法回填到原始报销单、采购单等明细记录上;而 SUM() OVER 可以按部门分区,逐行给出该部门的累计值或总计值。
常见错误现象:SELECT dept, amount, SUM(amount) FROM expense GROUP BY dept → 拿不到每条记录对应的部门总预算;或者误写成 SUM(amount) OVER (PARTITION BY dept ORDER BY time) 却忘了加 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,导致算的是“到当前行为止的滚动和”,而非“部门总额”。
怎么写才能得到准确的部门预算消耗率
消耗率 = 当前已用金额 / 部门总预算。关键在于:两个分子分母都得是窗口内计算出来的,且不能受当前查询条件意外过滤影响。
使用场景:你有一张 expense 表(字段:id, dept, amount, time),还有一张 budget 表(dept, total_budget)。通常需要先关联,再用窗口函数对齐粒度。
SELECT
e.dept,
e.amount,
e.time,
-- 部门总预算(取任意一行都一样,所以用 MAX 或 SUM 都行)
MAX(b.total_budget) OVER (PARTITION BY e.dept) AS dept_budget,
-- 部门已用总额(注意:这里必须用 SUM 不带 ORDER BY,否则是滚动和)
SUM(e.amount) OVER (PARTITION BY e.dept) AS dept_used,
-- 实时消耗率(注意 NULL 处理)
ROUND(
COALESCE(SUM(e.amount) OVER (PARTITION BY e.dept), 0)::DECIMAL
/ NULLIF(MAX(b.total_budget) OVER (PARTITION BY e.dept), 0),
4
) AS usage_rate
FROM expense e
JOIN budget b ON e.dept = b.dept;
参数差异点:
-
PARTITION BY e.dept必须写,否则全表算一个总数; - 不要加
ORDER BY到SUM(e.amount) OVER里,除非你真要滚动消耗率(比如“第5笔支出后累计用了多少”); -
MAX(b.total_budget) OVER是技巧:因为budget表每部门只有一行,关联后每个部门会重复多次,用MAX或MIN窗口函数能稳定取出该值。
为什么结果里出现 0.0000 或 NULL,常见坑在哪
- 部门在
expense 表里有记录,但没在 budget 表中配置 → 关联失败,b.total_budget 为 NULL → MAX(...) OVER 返回 NULL → 除零或 NULL 传播。
-
total_budget 值为 0 → NULLIF(..., 0) 正确返回 NULL,但若漏掉这层保护,会触发 division by zero 错误(不同数据库报错方式不同,PostgreSQL 报 division by zero,MySQL 可能静默转为 NULL)。
- 时间范围过滤写在
WHERE 子句里,却忘了它会先于窗口函数执行 → 导致 SUM() OVER 只基于过滤后的数据计算,不是“全量部门总额”。
expense 表里有记录,但没在 budget 表中配置 → 关联失败,b.total_budget 为 NULL → MAX(...) OVER 返回 NULL → 除零或 NULL 传播。total_budget 值为 0 → NULLIF(..., 0) 正确返回 NULL,但若漏掉这层保护,会触发 division by zero 错误(不同数据库报错方式不同,PostgreSQL 报 division by zero,MySQL 可能静默转为 NULL)。WHERE 子句里,却忘了它会先于窗口函数执行 → 导致 SUM() OVER 只基于过滤后的数据计算,不是“全量部门总额”。容易被忽略的地方:如果你的业务要求“仅统计本月支出”,但消耗率分母仍需是全年预算总额,那就不能把时间条件放在 WHERE,而要用条件聚合:
SUM(CASE WHEN e.time >= '2024-01-01' THEN e.amount ELSE 0 END) OVER (PARTITION BY e.dept) AS used_this_month而不是靠
WHERE e.time >= ... 去筛。

















