直接用PARTITION BY无法得到准确占比,因未处理分母为零、整数除法截断及NULL值问题;正确做法是用SUM() OVER(PARTITION BY dept)作分子、SUM() OVER()作分母,配合NULLIF防除零、显式类型转换保精度、ROUND控小数位。

为什么直接用 PARTITION BY 无法得到占比
因为 PARTITION BY 只负责分组“窗口”,不提供聚合结果的引用能力;你不能在同一个 SELECT 中直接用 SUM(salary) OVER (PARTITION BY dept) 除以 SUM(salary) OVER () 得到占比——语法上允许,但逻辑上容易漏掉 NULL 或精度问题,更关键的是:**没处理分母为零风险**。
正确写法:用 SUM() OVER() 做分子和分母再相除
核心是把部门薪资总和作为分子,公司总薪资作为分母,二者都用窗口函数算,然后相除。注意必须显式转成浮点,否则整数除法会截断为 0(尤其在 PostgreSQL / SQL Server 中)。
常见写法示例:
SELECT
dept,
salary,
ROUND(
CAST(SUM(salary) OVER (PARTITION BY dept) AS DECIMAL(10,2))
/ NULLIF(SUM(salary) OVER (), 0), 4
) AS dept_salary_ratio
FROM employees;
关键点:
-
NULLIF(SUM(...) OVER (), 0)防止除零错误,返回 NULL 而非报错 -
CAST(... AS DECIMAL(10,2))或salary * 1.0强制浮点运算(MySQL 可用salary / 1.0) -
ROUND(..., 4)控制小数位,避免浮点误差累积(比如 0.333333333)
GROUP BY 和窗口函数混用时的典型错误
有人想先 GROUP BY dept 再算占比,结果发现没法同时查员工明细。这是混淆了聚合层级:PARTITION BY 是保留原始行数的窗口计算,GROUP BY 是压缩行数的聚合。要查每个员工所属部门的薪资占比,必须用窗口函数,不能用 GROUP BY。
错误示范(会报错或逻辑错):
SELECT dept, SUM(salary) / SUM(salary) OVER() FROM employees GROUP BY dept;
问题在于:SUM(salary) 是聚合值,而 SUM(salary) OVER() 是窗口值,二者不在同一计算层级,多数数据库(如 PostgreSQL、SQL Server)会拒绝这种混合。
不同数据库对除零和类型转换的处理差异
MySQL 默认允许整数除 0 返回 NULL,但不报错;PostgreSQL 会直接抛 division by zero 错误;SQL Server 同样报错。所以 NULLIF(..., 0) 是跨平台安全写法。
类型转换也要看环境:
- PostgreSQL:推荐
(SUM(salary) OVER (PARTITION BY dept)::DECIMAL / NULLIF(SUM(salary) OVER (), 0)) - MySQL:可用
SUM(salary) OVER (PARTITION BY dept) / NULLIF(SUM(salary) OVER (), 0) + 0.0 - SQL Server:用
CONVERT(DECIMAL(5,4), ...)或乘1.0
实际跑之前,最好用 SELECT SUM(salary) OVER (), COUNT(*) FROM employees 先确认分母不为零、数据无异常 NULL 薪资。











