mysql 8.0+等主流数据库可用sum() over()直接计算占比,避免子查询性能抖动;全局占比用sum(salary) over()作分母,分组占比需加partition by;须用nullif防除零、100.0防整数截断。

用窗口函数直接算占比最稳
直接在 SELECT 里用 SUM(salary) OVER() 获取公司总工资,再除以当前行的 salary,就能避免子查询或 JOIN 带来的性能抖动和 NULL 风险。MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持这个写法。
- 别写
(SELECT SUM(salary) FROM employees)放在 SELECT 里——每行都执行一次,数据量大时明显变慢 - 如果某部门有 0 人,
COUNT(*)或GROUP BY dept后再算占比,会漏掉空部门;而窗口函数天然保全所有行(只要原始表里有该部门记录) - 注意除零:
ROUND(salary * 100.0 / NULLIF(SUM(salary) OVER(), 0), 2),NULLIF比CASE WHEN更简洁
GROUP BY 后再算占比的典型写法
如果只要部门维度聚合结果(不要员工明细),必须先 GROUP BY dept,否则窗口函数会按原始粒度计算,导致“部门工资合计 / 公司总工资”变成多行重复值。
- 正确顺序:
SELECT dept, SUM(salary) AS dept_sum, ROUND(SUM(salary) * 100.0 / SUM(SUM(salary)) OVER(), 2) AS pct FROM employees GROUP BY dept - 这里嵌套了
SUM(SUM(salary)) OVER()—— 外层SUM()是窗口函数,内层SUM()是聚合函数,语法合法且高效 - 别漏掉
AS dept_sum别名,否则某些数据库(如旧版 MySQL)会报 “Unknown column”
兼容老版本 MySQL(5.7 及更早)怎么办
没有窗口函数?用 JOIN 或子查询是唯一办法,但得小心 NULL 和笛卡尔积。
- 推荐写法:
SELECT e.dept, SUM(e.salary) / t.total * 100 AS pct FROM employees e CROSS JOIN (SELECT SUM(salary) AS total FROM employees) t GROUP BY e.dept -
CROSS JOIN比JOIN ... ON 1=1更清晰,也比把子查询放 SELECT 列里更易读 - 如果
employees表为空,t.total为 NULL,整个pct就全 NULL —— 生产环境建议加WHERE t.total IS NOT NULL过滤
百分比显示常见陷阱
业务报表里常要保留两位小数并带 % 符号,但字符串拼接会破坏数值排序和导出兼容性。
- 前端或 BI 工具里做格式化最安全;非要 SQL 里拼,用
CONCAT(ROUND(..., 2), '%'),但导出到 Excel 会被当文本 - 别用
PERCENT_RANK()或RANK()—— 这些是排名函数,不是占比计算,名字容易误导 - 浮点精度问题:
100.0而不是100,确保除法结果是 decimal/float,避免整数截断(比如 PostgreSQL 中5 / 10得 0)
实际跑的时候,先看执行计划里有没有出现 DEPENDENT SUBQUERY,有就说明用了低效的子查询写法;再确认 NULL 值是否被合理跳过——这两处最容易在线上突然出错。











