正确写法是sum(salary) over(partition by dept order by salary rows unbounded preceding)/sum(salary) over(partition by dept),需用100.0*防整除、nullif防零除、order by加唯一列保排序稳定。

用 SUM() OVER() 计算分组内累计占比的正确写法
直接在 GROUP BY 后套 SUM() OVER() 会报错或结果错乱——因为窗口函数不能和聚合混用在同一层。必须先聚合,再开窗,或者用子查询/CTE 拆开两步。
典型错误写法:SELECT dept, SUM(salary), SUM(salary) / SUM(SUM(salary)) OVER (PARTITION BY dept) —— 这里嵌套 SUM(SUM()) 语法非法,且 OVER 位置没对齐分组粒度。
- 正确路径:先按
dept, emp_id(或其他明细维度)聚合或保留明细,再用SUM(salary) OVER (PARTITION BY dept ORDER BY salary ROWS UNBOUNDED PRECEDING)算累计和 - 若原始表是明细数据(每人一行),直接开窗即可;若是已聚合表(如每部门一行),需先
UNPIVOT或改用变量模拟,不推荐 -
ORDER BY必须明确——没它就没有“累计”顺序,ROWS UNBOUNDED PRECEDING是默认但建议显式写出,避免不同数据库行为差异
计算“部门内薪资累计百分比”的完整 SQL 示例
假设表 emp 包含字段 dept、emp_name、salary,目标是每个部门内按薪资升序排列,算每个人及其之前所有人的薪资占本部门总薪资的比例。
SELECT
dept,
emp_name,
salary,
SUM(salary) OVER (PARTITION BY dept ORDER BY salary ROWS UNBOUNDED PRECEDING) AS cum_salary,
ROUND(
100.0 * SUM(salary) OVER (PARTITION BY dept ORDER BY salary ROWS UNBOUNDED PRECEDING) /
SUM(salary) OVER (PARTITION BY dept),
2
) AS cum_pct
FROM emp;
注意:SUM(salary) OVER (PARTITION BY dept) 是部门总和,不带 ORDER BY,它会在每行重复出现,用于做分母;而分子是带 ORDER BY 的累计和。
- 百分比用
100.0 *而非100 *,避免整数除法截断(尤其在 PostgreSQL / SQL Server 中) - MySQL 8.0+、PostgreSQL、Oracle、SQL Server 2012+ 都支持;SQLite 目前不支持窗口函数
- 如果想按入职时间排序而非薪资,把
ORDER BY salary换成ORDER BY hire_date即可,逻辑不变
常见翻车点:NULL、重复值、排序稳定性
当 salary 为 NULL,默认会被排在最前面(取决于数据库 NULLS FIRST/LAST 设置),导致累计和从 NULL 开始,整列变 NULL。重复薪资值也会引发排序不稳定——同一薪资的两人谁先谁后不确定,累计百分比可能每次执行微调。
- 加
COALESCE(salary, 0)或WHERE salary IS NOT NULL显式过滤/补零 - 排序键尽量复合:
ORDER BY salary, emp_id,用主键兜底保证确定性 - 别依赖默认
NULLS行为,显式写ORDER BY salary NULLS LAST(PostgreSQL/Oracle)或等价处理 - 某些旧版 MySQL(
替代方案:用 CTE 预先算好分组总数再 JOIN
当逻辑复杂或需多次引用分组总计时,CTE 比重复写 SUM() OVER (PARTITION BY ...) 更清晰、更易调试。
WITH dept_total AS ( SELECT dept, SUM(salary) AS total_salary FROM emp GROUP BY dept ) SELECT e.dept, e.emp_name, e.salary, SUM(e.salary) OVER (PARTITION BY e.dept ORDER BY e.salary) AS cum_salary, ROUND(100.0 * SUM(e.salary) OVER (PARTITION BY e.dept ORDER BY e.salary) / dt.total_salary, 2) AS cum_pct FROM emp e JOIN dept_total dt ON e.dept = dt.dept;
这种写法把“分母”抽出来单独算,避免了窗口函数里嵌套聚合的语义混淆,也方便后续加条件(比如只看 salary > 5000 的人)。
真正难的不是写法,而是想清楚“累计”基于什么排序、分母是否固定、NULL 怎么参与计算——这些决定了结果是否可解释、能否复现。











