first_value返回的不是分组第一条,是因为必须显式指定partition by和order by才能定义分组与排序逻辑;漏写partition by会导致全表视为一个组,缺order by则结果不确定;默认窗口范围range between unbounded preceding and current row对first_value虽不影响首值获取,但为保证语义清晰仍建议明确分组与排序。

FIRST_VALUE 为什么返回的不是分组第一条?
因为 FIRST_VALUE 默认窗口范围是 UNBOUNDED PRECEDING TO CURRENT ROW,它不自动按组切分——你得显式定义 OVER 子句里的 PARTITION BY 和 ORDER BY,否则它可能在整个结果集上取第一个值,而不是每组第一个。
- 漏写
PARTITION BY→ 所有行共用一个“组”,只返回全局首条 -
ORDER BY字段不稳定(比如多个相同时间戳)→ 结果非确定,不同执行可能取不同行 - 没加
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING→ 在默认范围内,CURRENT ROW后面的行还没被纳入窗口,可能导致中间行拿不到真正首值
正确写法:必须同时指定 PARTITION BY 和 ORDER BY
想查每个部门薪资最高的人,不能只靠 ORDER BY salary DESC,还得按部门分组。典型写法:
SELECT
name,
dept,
salary,
FIRST_VALUE(name) OVER (
PARTITION BY dept
ORDER BY salary DESC, name ASC
) AS top_earner_in_dept
FROM employees;
注意两点:
-
PARTITION BY dept确保每个部门独立计算 -
ORDER BY salary DESC, name ASC解决并列情况,保证结果可重现 - 不需要手动加
ROWS范围——只要明确PARTITION BY+ORDER BY,数据库就能在每组内正确锚定首行
和 LIMIT 1 + GROUP BY 的本质区别
FIRST_VALUE 是窗口函数,保留原始行数;而 GROUP BY + LIMIT 1 会聚合掉其他行。例如要标记“本组是否为首条”,只能用窗口函数:
SELECT *,
CASE WHEN name = FIRST_VALUE(name) OVER (PARTITION BY dept ORDER BY hire_date)
THEN 'first_hired' ELSE '' END AS flag
FROM employees;
这种需求下:
- 用
GROUP BY dept会丢失除dept外的所有字段,无法逐行打标 -
FIRST_VALUE不改变行数,适合做“行级上下文判断” - 性能上,窗口函数通常比多次子查询或自连接更高效,但大数据量时仍需注意排序开销
MySQL 8.0+ 和 PostgreSQL 的兼容性细节
两者语法一致,但 MySQL 在早期版本(ERROR 3594: Window function is not allowed in this context。PostgreSQL 则从 9.4 开始支持。
- 确认版本:
SELECT version();—— MySQL 必须 ≥ 8.0.0,PostgreSQL 必须 ≥ 9.4 - Oracle、SQL Server、BigQuery 均支持,语义相同
- SQLite 直到 3.25.0(2018年)才加入,旧版会报
no such function: FIRST_VALUE
实际部署前务必验证目标环境是否启用窗口函数,别只在本地测试通过就上线。











