累计占比分母必须是当前分组总和,需用SUM() OVER(PARTITION BY category)而非全表SUM() OVER();须用NULLIF防除零、100.0防整除截断、COALESCE处理空分子;ORDER BY决定累计顺序,不可遗漏。

累计占比必须用窗口函数算,嵌套查询只适合提供静态分母——混用时最容易把“全表总和”当“分组总和”,结果全错。
为什么不能只用 SUM() OVER() 直接除?
因为 SUM() OVER() 默认是全表范围,而累计占比的分母必须是“当前分组内总数”。比如按 category 分组后,某月累计销量占本 category 总销量的比例,分母要是 SUM(sales) OVER (PARTITION BY category),不是 SUM(sales) OVER ()。
- 错误写法:
SUM(sales) OVER (PARTITION BY category ORDER BY date) / SUM(sales) OVER ()—— 分母没PARTITION BY,用了全表总和 - 正确分母必须带
PARTITION BY category,且不加ORDER BY(否则变成累计分母) - 如果分母本身也带
ORDER BY,就会随行变化,导致每行除出来都是 1.0 或接近 1 的值
NULLIF 和 100.0 不是可选项,是必填项
除零报错、整数截断,是线上 SQL 突然崩掉的常见原因。尤其在 MySQL/SQL Server 中,5 / 8 结果是 0,不是 0.625。
- 强制浮点:用
100.0而不是100,或显式CAST(col AS DECIMAL(10,4)) - 防除零:分母一律包
NULLIF(denom, 0),例如num * 100.0 / NULLIF(total_in_group, 0) -
COALESCE要提前用:如果分子字段可能为NULL,先COALESCE(sales, 0),否则整组占比会变NULL
嵌套查询只该干一件事:提供固定分母
当你需要“截至当月的累计销量占全年目标的百分比”,目标值是外部给定(如配置表里存的 1000.0),这时才用嵌套查询或 CTE 提供分母。
- 推荐写法:
WITH target AS (SELECT 1000.0 AS goal) SELECT ROUND(SUM(sales) * 100.0 / (SELECT goal FROM target), 2) ... - 别在子查询里再套窗口函数,比如
(SELECT SUM(sales) OVER (PARTITION BY year))—— 语法错,且语义混乱 - 生产环境的目标值建议存在配置表,用
JOIN替代硬编码,避免改 SQL
ORDER BY 在累计窗口里决定“累计顺序”,漏写就白算
SUM() OVER (PARTITION BY x ORDER BY y) 的 ORDER BY 不是可选装饰,它定义了累计的行序。时间倒序、ID 乱序、空值排最前,都会让累计值反常。
- 时间类累计必须明确
ORDER BY order_date,且确认order_date非空、无重复(重复时加id辅助排序) - 默认 frame 是
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不用显写,但心里得有数 - 如果业务要求“按金额从高到低累计”,那就
ORDER BY amount DESC,顺序错了,占比曲线就颠倒
真正难的不是写对一行 SQL,而是搞清“这个分母到底该是全表、分组、还是外部固定值”——多数线上问题,都卡在这一步的语义判断上。

















