直接用lag算调薪幅度易出错,因未指定partition by和order by会导致跨员工/部门数据混淆;须按emp_id、dept_id分组并按salary_year排序,配合coalesce与nullif防null及除零,mysql 8.0以下可用关联子查询替代。

为什么直接用 LAG 算调薪幅度容易出错
因为 LAG 默认只取上一行,而员工调薪记录未必按时间严格连续——可能有空缺年份、多条同年度记录、或跨部门调动。如果没显式指定 ORDER BY 和 PARTITION BY,LAG 会把整个结果集当一个组处理,导致张三的薪资被李四的前一条覆盖。
正确做法是:按 dept_id 分组,再按 salary_year 升序排序,确保“上一次调薪”确实是本部门内该员工的前一次。
-
PARTITION BY emp_id, dept_id比只写PARTITION BY dept_id更安全——避免不同员工数据串行 - 必须用
ORDER BY salary_year,不能用id或插入顺序,否则时间逻辑错乱 - 若存在同一年两条调薪记录(如年中普调+年终激励),需先去重或明确业务规则(比如取
MAX(salary))
怎么写一个带空值防护的调薪幅度计算
调薪幅度 = (当前薪资 − 上次薪资) / 上次薪资,但 LAG 对首条记录返回 NULL,直接参与除法会得 NULL,且除零也要防。
推荐用 COALESCE + NULLIF 组合:
SELECT
emp_id,
dept_id,
salary_year,
current_salary,
LAG(current_salary) OVER (
PARTITION BY emp_id, dept_id
ORDER BY salary_year
) AS prev_salary,
COALESCE(
ROUND(
(current_salary - LAG(current_salary) OVER (
PARTITION BY emp_id, dept_id
ORDER BY salary_year
)) * 100.0 /
NULLIF(LAG(current_salary) OVER (
PARTITION BY emp_id, dept_id
ORDER BY salary_year
), 0),
2),
0
) AS raise_pct
FROM salary_history;
-
NULLIF(..., 0)把分母为 0 的情况转成NULL,避免除零错误 -
COALESCE(..., 0)把首年(prev_salary为NULL)、或分母为 0 导致的NULL统一置为 0 - 乘以
100.0是为了强制转浮点,避免整数除法截断(尤其在 PostgreSQL / SQL Server 中)
MySQL 8.0 以下版本没有 LAG 怎么办
只能用自连接或变量模拟,但变量方式不稳定(执行计划变化可能打乱顺序),更可靠的是关联子查询:
SELECT
s1.emp_id,
s1.dept_id,
s1.salary_year,
s1.current_salary,
(SELECT s2.current_salary
FROM salary_history s2
WHERE s2.emp_id = s1.emp_id
AND s2.dept_id = s1.dept_id
AND s2.salary_year
- 性能差:每行都要跑三次子查询,数据量大时明显变慢
- 必须给
(emp_id, dept_id, salary_year)建联合索引,否则全表扫描 - 如果允许降级到应用层计算(比如 Python/Pandas),反而比硬写 SQL 更可控
实际业务中常被忽略的边界情况
调薪幅度不是纯数学题,HR 系统里几个细节不处理,报表就和人力部对不上:
- 试用期转正不算调薪——需过滤掉
change_type = 'probation_to_regular'这类非薪资调整记录 - 跨部门调动后首次薪资,应视为新序列起点,不能和原部门的末次薪资比较
- 外派补贴、一次性奖金混在
current_salary字段里?得先清洗,否则幅度失真 - 财务月结时间晚于调薪生效日(如 4 月 1 日调薪,但 4 月 25 日才入账),查表时要用
effective_date而非update_time
这些逻辑没法靠 LAG 自动识别,得在 WHERE 或预处理 CTE 里提前筛干净。










