计算分组内占比的核心是:用value*1.0除以sum(value)over(partition by category),需显式写分子、防整数截断和除零,null需coalesce处理,多列分组直接逗号分隔,禁用order by以免误算累计值。

用 SUM() OVER() 计算分组内占比最直接
核心思路是:先用窗口函数算出每个分组的总和,再用当前行值除以该分组总和。不能用 GROUP BY 后再除,否则会丢失明细行。
常见错误是写成 SUM(value) / SUM(value) OVER(PARTITION BY category) 却忘了当前行的 value 没被聚合——必须显式写出分子,比如 value * 1.0 避免整数截断。
-
value * 1.0 / SUM(value) OVER(PARTITION BY category)是安全写法 - 如果
value是DECIMAL或已含小数,可省略* 1.0 - 分母加
COALESCE(..., 0)能防除零,但更推荐提前过滤掉分母为 0 的组
遇到 NULL 值时,SUM() OVER() 默认跳过不影响结果
SUM() 窗口函数天然忽略 NULL,这点和普通 SUM() 一致。但分子如果是 NULL,整个占比就会变成 NULL——这不是窗口函数的问题,而是除法运算规则。
如果你希望把 NULL 当 0 参与计算,得主动处理:
- 分子用
COALESCE(value, 0) - 分母仍用
SUM(value) OVER(PARTITION BY category)(它本身已跳过NULL) - 若整组
value全是NULL,分母为 0,需额外判断,例如:CASE WHEN SUM(value) OVER(PARTITION BY category) = 0 THEN 0 ELSE COALESCE(value, 0) * 1.0 / NULLIF(SUM(value) OVER(PARTITION BY category), 0) END
想按多列分组?PARTITION BY 支持逗号分隔列表
比如按部门和年份联合统计占比,直接写 PARTITION BY dept, year 即可,不需要嵌套或子查询。
注意顺序无关,但列名必须明确存在且类型兼容;如果其中一列有大量 NULL,它们会被视为同一组——这是标准 SQL 行为,不是 bug。
-
PARTITION BY region, product_type是合法且高效的 - 避免在
PARTITION BY里用表达式如UPPER(name),部分数据库不支持(如 MySQL 8.0+ 支持,PostgreSQL 支持,SQLite 不支持) - Oracle 和 SQL Server 对分区键数量无硬限制,但超过 5 列可能影响执行计划稳定性
性能敏感时,别在 ORDER BY 窗口里做占比计算
如果误写成 SUM(value) OVER(PARTITION BY category ORDER BY id),得到的是“截止到当前行”的累计占比,不是全组占比。这容易被当成 bug,尤其当数据有序且结果看起来“差不多”时。
确认是否真需要累计逻辑——90% 的分组占比场景,应严格使用无 ORDER BY 的窗口定义。
- 正确:
SUM(value) OVER(PARTITION BY category) - 错误(除非明确要累计):
SUM(value) OVER(PARTITION BY category ORDER BY created_at) - 某些数据库(如 BigQuery)对带
ORDER BY的聚合窗口会强制排序,显著拖慢大表查询
实际跑起来之前,先用 SELECT category, value, SUM(value) OVER(PARTITION BY category) AS group_total 看一眼分母是否符合预期——这个动作比查文档更快定位问题。











