窗口函数不支持where,须用case when过滤;sum等聚合需else 0防null断层,count需then 1不写else;order by在累计计算中不可省;filter子句(pg)更高效,mysql/旧版sql server需硬写case。

窗口函数里不能直接用 WHERE,得靠 CASE WHEN 过滤行
窗口函数(如 SUM()、AVG())本身不支持 WHERE 子句,想“只对满足条件的行做聚合”,必须把过滤逻辑写进聚合表达式内部。常见错误是写成 SUM(col) WHERE status = 'done' —— 这语法根本不存在,会报错 ERROR: syntax error at or near "WHERE"。
正确做法是用 CASE WHEN 把不满足条件的行转为 NULL 或 0,再交给窗口函数处理:
SELECT
id,
status,
amount,
SUM(CASE WHEN status = 'done' THEN amount ELSE 0 END)
OVER (PARTITION BY category ORDER BY created_at) AS cumsum_done
FROM orders;
-
ELSE 0更安全:避免SUM()跳过NULL导致累计值断层(比如前几行都是NULL,SUM返回NULL而非0) - 如果聚合的是计数(
COUNT),记得用COUNT(CASE WHEN ... THEN 1 END),不能写COUNT(CASE WHEN ... THEN 1 ELSE 0 END)—— 因为COUNT会统计所有非NULL值,0也会被计入 -
ORDER BY在OVER中不是可选的:做累计类计算时漏写会导致结果无序、不可复现
CASE WHEN 嵌套在 ROW_NUMBER() / RANK() 里没意义
像 ROW_NUMBER() OVER (ORDER BY CASE WHEN flag=1 THEN score END) 这种写法看似在“按条件排序”,但实际会把不满足条件的 score 变成 NULL,而 NULL 在多数数据库中默认排最前或最后(行为因引擎而异),极易引发排序错乱。
真正需要“条件排序”时,应该明确补全分支:
ROW_NUMBER() OVER (
ORDER BY
CASE WHEN flag = 1 THEN 0 ELSE 1 END, -- 先分组:flag=1 的排前面
CASE WHEN flag = 1 THEN score END DESC, -- 再按 score 降序
id -- 最后用主键保序
)
- 不要依赖
CASE返回NULL来“排除”某些行——窗口函数仍会为其分配序号,只是排序键为NULL -
RANK()和DENSE_RANK()对NULL的处理逻辑和ROW_NUMBER()一致,同样不跳过 - PostgreSQL 支持
NULLS FIRST/LAST显式控制,但 MySQL 8.0+ 和 SQL Server 不支持该语法,需靠CASE拆解
多个条件组合时,CASE WHEN 顺序决定结果
窗口聚合中嵌套多层条件,CASE 的分支顺序直接影响计算逻辑。例如想“只统计已完成且金额 > 100 的订单”,下面两种写法结果不同:
-- ✅ 正确:先判断 status,再筛 amount
SUM(CASE WHEN status = 'done' THEN
CASE WHEN amount > 100 THEN amount ELSE 0 END
ELSE 0 END) OVER (...)
<p>-- ❌ 错误:逻辑等价但易读性差,且一旦 status 不是 'done',amount > 100 判断不会执行(短路不保证)
SUM(CASE WHEN status = 'done' AND amount > 100 THEN amount ELSE 0 END) OVER (...)</p>
- 推荐扁平化写法:
CASE WHEN cond1 AND cond2 THEN ...,语义清晰,各数据库优化器也更容易识别 - 嵌套
CASE仅在需要“分阶段映射”时使用(例如:先把状态映射为优先级数字,再按数字排序) - 注意
AND表达式中字段可能为NULL:若amount可空,amount > 100结果为UNKNOWN,整条WHEN不匹配,需显式写成amount IS NOT NULL AND amount > 100
性能敏感场景下,CASE WHEN 窗口聚合可能拖慢查询
当数据量大(千万级以上)、窗口范围宽(如 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)、且 CASE 分支复杂时,每个窗口帧都要重复计算条件表达式,CPU 开销明显上升。
- 先确认是否真需要窗口计算:如果只是按组汇总,用普通
GROUP BY+HAVING更快 - 把
CASE提前物化成计算列(如建生成列或 CTE),再在窗口函数中引用,可减少重复计算 - 在 PostgreSQL 中,
filter子句是更简洁高效的替代方案:SUM(amount) FILTER (WHERE status = 'done') OVER (...),语义等价且通常更快 - MySQL 不支持
FILTER,SQL Server 需用OVER (… WHERE …)(仅限 2022+ 版本),老版本只能硬写CASE
最常被忽略的一点:CASE WHEN 里的字段如果没有出现在 PARTITION BY 或 ORDER BY 中,又参与了窗口计算,就可能触发隐式全局排序,尤其在分布式数据库(如 Greenplum、StarRocks)上容易 OOM。










