必须用partition by employee_id和order by hire_date asc配合first_value(salary),否则无法准确获取每人最早入职时的工资;默认无序或误用order by salary会导致结果错误。

用 FIRST_VALUE 按员工分组取最早入职时的工资
直接结论:必须配合 ORDER BY hire_date 和 PARTITION BY employee_id 使用,否则 FIRST_VALUE(salary) 返回的不是“入职时的初始工资”,而是窗口内默认排序下的第一个值——而默认排序不可控,结果会错。
常见错误现象:FIRST_VALUE(salary) OVER (PARTITION BY employee_id) 没写 ORDER BY,结果随机;或误把 ORDER BY salary 当成按时间排,实际取到的是最低工资而非最早工资。
- 必须显式按入职时间升序:
ORDER BY hire_date ASC(ASC可省略,但建议写明) -
PARTITION BY employee_id确保每个员工独立计算,不跨人混算 - 窗口框架默认是
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,对FIRST_VALUE无影响,不用改 - 如果表中存在同一员工多条入职记录(如历史调岗重录),需先去重或明确业务定义“初始”指哪一条
FIRST_VALUE 和 MIN/LAG 的本质区别
FIRST_VALUE 是取窗口内排序后第一行的指定列值,它保留原始行上下文;而 MIN(salary) 只返回最小数值,丢失了该最小值对应的 hire_date;LAG 则依赖当前行位置,无法直接定位“第一条”。
使用场景:你要的不是“最低工资”,而是“这个人第一次进公司那天发的工资是多少”,哪怕他后来降薪了,也要那个时间点的数字。
- 正确:
FIRST_VALUE(salary) OVER (PARTITION BY employee_id ORDER BY hire_date) - 错误:
MIN(salary) OVER (PARTITION BY employee_id)—— 可能返回转正后调薪前的更低值 - 错误:
LAG(salary) OVER (PARTITION BY employee_id ORDER BY hire_date)—— 第一行的LAG是NULL,拿不到初始值
兼容性与 NULL 处理要注意
MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持 FIRST_VALUE,但 SQLite 不支持;旧版 MySQL(5.7 及之前)也不支持窗口函数。
如果某员工的 salary 在首条记录里是 NULL,FIRST_VALUE 就返回 NULL——它不会跳过 NULL 找下一个非空值。这点和聚合函数不同。
- 想跳过
NULL?得先过滤或用CASE预处理,例如:FIRST_VALUE(CASE WHEN salary IS NOT NULL THEN salary END) ...,但注意这会导致整个表达式可能为NULL(当所有 salary 都为空时) - PostgreSQL 中可加
IGNORE NULLS(FIRST_VALUE(salary) IGNORE NULLS OVER (...)),但 MySQL 和 SQL Server 不支持该语法 - 确认数据库版本再写,别在 MySQL 5.7 上调试半天发现语法报错
ERROR 1064
真实查询示例:带别名和去重的实用写法
假设表叫 employee_history,含字段 employee_id、hire_date、salary,且一人可能有多条记录(比如试用期和转正各一条),你想取每人的“首次有效入职工资”:
SELECT DISTINCT
employee_id,
FIRST_VALUE(salary) OVER (
PARTITION BY employee_id
ORDER BY hire_date ASC
) AS first_salary
FROM employee_history
WHERE salary IS NOT NULL;
这里用 DISTINCT 是因为窗口函数会在原行数基础上输出多行,而你通常只需要每人一条;WHERE 提前过滤掉 salary 为空的记录,比在窗口里处理更清晰可控。
容易被忽略的一点:FIRST_VALUE 返回的是窗口定义范围内的值,不是整张表扫描结果——如果你加了 WHERE hire_date >= '2020-01-01',那“第一次”就变成这个时间之后的第一次,而不是员工真正的入职时间。











