不能直接用 group by + sum 做分母,因为 group by 后 sum 只计算组内总和,无法获取全表总和;需用 sum() over() 窗口函数在每行提供全表聚合值,再与组内 sum 相除,并配合 nullif 和 coalesce 处理除零与 null。

为什么不能直接用 GROUP BY + SUM 做分母
因为 GROUP BY 会把数据按组切开,组内 SUM() 只能算本组总和,拿不到全表总和。想算“某组值 / 全表总和”,必须让每行都能访问到全表聚合结果——窗口函数正是干这事的。
常见错误是写成:SELECT dept, SUM(salary) / SUM(salary) FROM emp GROUP BY dept,这其实等价于 1.0,毫无意义。
SUM() OVER() 是最直接的写法
用 SUM(salary) OVER() 得到全表 salary 总和,它会在每一行重复出现,和当前行的组内聚合值(比如 SUM(salary))做除法即可。
- 必须搭配
GROUP BY先聚合同组数据,再在 SELECT 中引用窗口函数 -
SUM() OVER()不带PARTITION BY就是全表范围;加了才是按某列分组窗口 - 注意类型:整数除法可能截断,建议显式转
DECIMAL或乘 1.0
SELECT dept, SUM(salary) AS dept_sum, ROUND(SUM(salary) * 1.0 / SUM(salary) OVER(), 4) AS ratio FROM emp GROUP BY dept;
避免 NULL 和除零问题
如果全表 SUM(salary) 是 0 或 NULL,除法结果会是 NULL 或报错(取决于数据库)。生产环境必须兜底。
- 用
NULLIF(SUM(salary) OVER(), 0)把分母 0 转成 NULL,使整除结果为 NULL 而非报错 - 再用
COALESCE(..., 0)把 NULL 统一转成 0,语义更明确 - PostgreSQL、SQL Server、Oracle 都支持;MySQL 8.0+ 同样适用
SELECT
dept,
COALESCE(
SUM(salary) * 1.0 / NULLIF(SUM(salary) OVER(), 0),
0
) AS ratio
FROM emp
GROUP BY dept;
性能和可读性取舍:WHERE 过滤后才计算比例
如果只关心活跃部门(比如 status = 'active'),务必先过滤再算窗口总和。否则 SUM() OVER() 仍基于原始全表,比例失真。
- 错误写法:
SELECT ... FROM emp GROUP BY dept WHERE status = 'active'(WHERE 不能放 GROUP BY 后) - 正确顺序:子查询或 CTE 先过滤,再聚合 + 窗口
- CTE 更清晰:
WITH filtered AS (SELECT * FROM emp WHERE status = 'active')
窗口函数本身不拖慢查询,但若 OVER() 范围过大(如含 ORDER BY 和 ROWS BETWEEN),会影响性能;纯 SUM() OVER() 几乎无开销。










