百分比计算不能只用GROUP BY,因为分组后丢失全局总量;需先子查询或CTE聚合各组值,再用SUM() OVER()补全量,最后相除并处理除零、NULL和精度问题。

百分比计算为什么不能只用 GROUP BY
只靠 GROUP BY 拿不到总量,因为分组后每组独立聚合,原始总和信息丢失。比如按部门统计人数,COUNT(*) 能出各部门人数,但无法直接除以公司总人数——那个总数不在当前分组结果里。
常见错误写法:SELECT dept, COUNT(*) / COUNT(*) OVER() FROM emp GROUP BY dept,这会报错:窗口函数不能和 GROUP BY 混用在同一个 SELECT 层级(除非子查询或 CTE)。
SUM OVER 配合子查询是最稳的写法
先用子查询算出各组聚合值,再在外层用窗口函数补上全局总量,避免逻辑冲突。
- 先聚合:用
GROUP BY得到每组的计数/求和值,比如COUNT(*) AS cnt - 再补总量:外层查这个结果集,用
SUM(cnt) OVER()拿到所有组的 cnt 总和 - 最后相除:注意显式转成
DECIMAL或FLOAT,否则整数除整数得 0(如 PostgreSQL/SQL Server 默认截断)
示例(统计各部门人数占全公司比例):
SELECT dept, cnt, ROUND(100.0 * cnt / SUM(cnt) OVER(), 2) AS pct FROM ( SELECT dept, COUNT(*) AS cnt FROM emp GROUP BY dept ) t;
想一步到位?用 CTE 更清晰且可复用
CTE 把中间聚合结果命名,后续引用更直观,也方便加其他维度(比如同时看同比、环比)。
- CTE 内完成
GROUP BY和基础聚合 - 主查询中直接用
SUM(...) OVER()算分母,不依赖外部表 - MySQL 8.0+、PostgreSQL、SQL Server 都支持;旧版 MySQL 只能靠子查询
示例:
WITH dept_cnt AS ( SELECT dept, COUNT(*) AS cnt FROM emp GROUP BY dept ) SELECT dept, cnt, ROUND(100.0 * cnt / SUM(cnt) OVER(), 2) AS pct FROM dept_cnt;
小心除零、NULL 和精度丢失
如果某组数据为空(COUNT(*) 为 0),或整个表为空(SUM(cnt) OVER() 为 0),直接除会出错或返回 NULL。百分比字段显示为 NULL 往往不是语法问题,而是数据本身导致。
- 加
NULLIF(SUM(cnt) OVER(), 0)防止除零:如cnt / NULLIF(SUM(cnt) OVER(), 0) - 用
COALESCE(..., 0)把最终 NULL 替换成 0,适合报表展示 -
ROUND(..., 2)后仍是浮点近似值,做累计校验时可能有 0.01% 偏差,生产环境建议加ABS(SUM(pct) - 100) > 0.01告警
真正难的不是写对那行 SUM(cnt) OVER(),而是确认「总量」到底该包含哪些行——WHERE 条件是否已过滤?NULL 值是否参与计数?这些必须和业务方对齐,代码改起来快,理解错口径就全偏了。










