不能用普通sum(),因其会压缩行数丢失明细;必须用sum() over(partition by ...)保留每行并计算组内总和,再做比例运算。

直接用窗口函数 SUM() 配合 OVER(PARTITION BY ...) 就能算,不需要子查询或自连接。
为什么不能用普通 SUM()?
普通聚合函数(如 SUM(amount))会把整组压缩成一行,丢失原始行粒度,自然没法让每行除以组总和。窗口函数则保留每一行,只在逻辑分组内做计算。
- 错误写法:
SELECT category, amount / SUM(amount) FROM sales GROUP BY category→ 语法报错或结果错乱 - 正确思路:先用
SUM(amount) OVER (PARTITION BY category)算出每行所属组的总和,再做除法
SUM() OVER (PARTITION BY ...) 的实际写法
假设表 sales 有字段 category 和 amount,要算每个 amount 占本 category 总和的比例:
SELECT category, amount, ROUND(amount * 1.0 / SUM(amount) OVER (PARTITION BY category), 4) AS ratio FROM sales;
-
amount * 1.0是为避免整数除法截断(尤其在 PostgreSQL/SQL Server 中) -
ROUND(..., 4)控制小数位,防止浮点误差影响可读性 - 如果某组总和为 0,该组所有
ratio会是NULL(除零不报错,但结果为NULL),需按需加CASE WHEN处理
遇到 NULL 或 0 总和怎么办?
当某组 amount 全为 NULL 或显式为 0,SUM(...) OVER 返回 NULL 或 0,导致比例列全为 NULL 或报错(取决于数据库)。稳妥做法:
- 过滤掉无效组:
WHERE amount IS NOT NULL AND amount != 0 - 或用
CASE显式兜底:CASE WHEN SUM(amount) OVER (PARTITION BY category) = 0 THEN 0 ELSE amount * 1.0 / SUM(amount) OVER (PARTITION BY category) END - 注意:不同数据库对
SUM(NULL)的处理一致(返回NULL),但对0 / 0行为略有差异(PostgreSQL 报错,MySQL/SQL Server 返回NULL)
真正容易被忽略的是除零安全性和数据类型隐式转换——尤其是原始字段为整型时,不乘 1.0 可能导致整数截断,看着像 0 而不是 0.375。











