最稳算法是group by后套sum() over(partition by category),分母必须是窗口和而非普通sum,需round(100.0*sales/sum(sales)over(partition by category),2),并用coalesce处理null。

用 SUM() OVER() 算分组内占比最稳
直接在 GROUP BY 后再套一层窗口函数,是算各分组内占比最可靠的方式。它不依赖子查询、不放大行数、也不怕多维分组崩掉逻辑。
常见错误是先 GROUP BY 再用 SUM() 做除法——结果全错,因为分母变成整个表的总和,不是当前分组的总和。
- 正确写法:分母必须是
SUM(sales) OVER (PARTITION BY category),不是SUM(sales) - 场景举例:按
category分组看每个product占该类目的比例,就得在SELECT里同时保留category和窗口求和 - 如果漏写
PARTITION BY,就退化成全表占比,和预期完全不符
ROUND() 要紧跟着除法,别等聚合完再四舍五入
先算出小数再 ROUND(),比在整数阶段硬除更准。尤其当销售金额是整型时,sales / SUM(sales) 在某些数据库(如 PostgreSQL、SQL Server)里会截断为整数零。
- PostgreSQL 中
5 / 10得0,必须写成5.0 / 10或用CAST(sales AS DECIMAL) - 推荐统一写法:
ROUND(100.0 * sales / SUM(sales) OVER (PARTITION BY category), 2) - MySQL 8.0+ 和 BigQuery 对隐式类型提升友好些,但跨库迁移时这个写法最保险
遇到 NULL 销售额?COALESCE() 比 ISNULL() 更通用
只要字段可能为空,除法前不处理就会让整行占比变 NULL。不同数据库对空值参与运算的容忍度不一,但统一用 COALESCE() 最省心。
- 写成
COALESCE(sales, 0),确保分子不空;分母的窗口和也建议包一层:SUM(COALESCE(sales, 0)) OVER (...) - 别用
ISNULL(sales, 0)—— 这是 SQL Server 特有,MySQL 和 PostgreSQL 不认 - 注意:把空转成 0 会影响占比数值,如果业务上“未录销售额”和“零销售额”含义不同,得先明确清洗策略
大表慎用 OVER() 嵌套太多层级
单个 SUM() OVER() 没压力,但若叠加多个 PARTITION BY + ORDER BY(比如还要算累计占比),执行计划容易走坏,特别是没建好索引时。
- 检查执行计划里有没有
WindowAgg节点出现多次或数据重排(Sort)膨胀 - 分区字段(如
category)最好有索引,尤其是和时间字段组合的复合索引 - 如果只是静态报表,且数据量超千万,先写入临时表预计算分组和,再
JOIN回原表,有时比纯窗口快得多
真正难的不是写出那条语句,而是想清楚“占比”的分母到底属于哪一层粒度——是当前分组?还是某个维度组合?还是带过滤条件的子集?一旦 partition 逻辑和业务口径对不上,数字看着再整齐也没用。










