正确做法是用SUM() OVER (PARTITION BY group_col)计算组内总和,再用当前行值除以该值,并用COALESCE和NULLIF处理空值与零分母,避免报错或结果丢失。

GROUP BY 后怎么算每个组内占比?别直接除总和
直接用 SUM() 除以全局总和会丢失组内结构,结果全是同一值。正确做法是用窗口函数在组内做聚合,再做除法。
核心逻辑:先用 SUM() OVER (PARTITION BY group_col) 算出每行所属组的总和,再用当前行值除以这个组内总和。
- 错误写法:
col / SUM(col)→ 这会触发隐式分组,只剩一行 - 正确写法:
col * 1.0 / SUM(col) OVER (PARTITION BY category)→ 保留原行数,每行算自己在本组的占比 - 注意乘
1.0或转DECIMAL,避免整数除法截断(尤其在 PostgreSQL/SQL Server 中)
ORDER BY 在 SUM OVER 里加不加?看你要不要累积占比
SUM() OVER (PARTITION BY x ORDER BY y) 和不带 ORDER BY 的行为完全不同:前者是“组内逐行累加”,后者是“组内静态总和”。
- 要组内占比(如“手机销量占该品类总销量多少”)→ 不加
ORDER BY,只用PARTITION BY - 要累计占比(如“销量前 N 名占该品类多少”)→ 加
ORDER BY sales DESC,再除以无序的组总和 - MySQL 8.0+、PostgreSQL、SQL Server 都支持;SQLite 3.25+ 支持但语法稍异
NULL 值和 0 分母怎么处理?不加 COALESCE 就崩
组内全为 NULL 或所有值都是 0 时,SUM() OVER 返回 NULL 或 0,直接相除会得 NULL 或报错(如 SQL Server 的“除零错误”)。
- 安全写法:
COALESCE(col, 0) * 1.0 / NULLIF(SUM(COALESCE(col, 0)) OVER (PARTITION BY category), 0) -
NULLIF(..., 0)把分母为 0 变成NULL,避免报错;外层可再套COALESCE(..., 0)统一补 0 - 别漏掉分子的
COALESCE:否则某行col是NULL,整个占比变成NULL,而不是 0%
性能差得离谱?检查 PARTITION BY 列有没有索引
窗口函数本身不走索引,但 PARTITION BY 列如果参与 WHERE 或排序,没索引会导致全表扫描 + 临时排序,大数据量下明显变慢。
- 常见瓶颈:对大表按
user_id分组算占比,但user_id没索引 → 执行计划出现WindowAgg+Sort节点,I/O 暴涨 - 优化动作:给
PARTITION BY列建索引(如CREATE INDEX idx_orders_category ON orders(category);) - 注意:MySQL 对窗口函数的索引利用较弱,PostgreSQL 表现更好;若仍慢,考虑先
GROUP BY聚合再 JOIN 回原表
实际写的时候,最容易被跳过的其实是分母的 NULLIF 和分子的 COALESCE —— 表面看着能跑,一遇到空值或零值,线上报表就突然少数据或者报错。

















