NTH_VALUE是窗口函数,用于返回按ORDER BY排序后窗口帧内第N个位置的表达式值;而ROW_NUMBER()为每行分配唯一递增序号。关键区别在于:NTH_VALUE取值,ROW_NUMBER()编号;NTH_VALUE需显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING才能稳定获取全局第N值,否则默认帧范围常导致NULL;它适用于每行标注参考值(如“第二高薪资”),不适用于分页或筛选。

什么是NTH_VALUE,它和ROW_NUMBER有什么区别?
NTH_VALUE 是窗口函数,用于返回窗口帧内指定位置的表达式值,不是“第N条记录”,而是“按排序后第N个出现的值”——这个值可能重复,也可能因帧范围(ROWS BETWEEN)而不可达。它和 ROW_NUMBER() 本质不同:ROW_NUMBER() 给每行打唯一序号;NTH_VALUE 只是“取某个偏移位置上的值”,且该位置超出帧范围时返回 NULL。
常见错误现象:NTH_VALUE(salary, 2) OVER (ORDER BY salary DESC) 在只有1条记录时返回 NULL,不是报错,容易被忽略。
- 必须显式指定
ORDER BY,否则语法报错 - 默认帧范围是
UNBOUNDED PRECEDING AND CURRENT ROW,这意味着第2个值永远无法取到(除非当前行是第2行或之后) - 想稳定取“排名第二的薪资”,得改成
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
怎么写才能稳定拿到“全局第二高的salary”?
关键在控制窗口帧。默认帧只看到“前面+当前”,所以 NTH_VALUE(..., 2) 在第一行永远为 NULL。要取全局第N个,必须让帧覆盖全部数据。
SELECT
name,
salary,
NTH_VALUE(salary, 2) OVER (
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS second_highest_salary
FROM employees;
注意:UNBOUNDED FOLLOWING 是必须的;如果用 RANGE 替代 ROWS,遇到相同 salary 会合并位置,导致“第2个”实际跳过多个物理行。
- 用
ROWS:按物理行序定位,更可控 - 用
RANGE:按排序键值分组定位,相同 salary 算同一“位置”,NTH_VALUE(salary, 2)可能返回和第1名相同的值 - PostgreSQL 和 Oracle 支持
NTH_VALUE,MySQL 8.0+ 才支持,SQL Server 不支持(得用OFFSET-FETCH或子查询替代)
为什么NTH_VALUE结果全是NULL?排查三步
最常见原因不是语法错,而是帧范围 + 排序 + 数据分布共同导致目标位置不可达。
- 检查
OVER子句是否漏了ORDER BY—— 缺少则直接报错 - 确认帧范围:默认
CURRENT ROW结尾,NTH_VALUE(x, 3)在前两行必然为NULL - 验证排序键是否有足够多的非NULL值:如果
salary有大量NULL,且ORDER BY salary DESC把NULL排最后,那前N个有效值可能不足
临时调试技巧:先用 COUNT(*) OVER () 看总行数,再用 ROW_NUMBER() OVER (ORDER BY salary DESC) 看实际排名,对比 NTH_VALUE 的输出位置是否合理。
替代方案:什么时候不该用NTH_VALUE?
如果目标只是“取第N条记录”,比如分页查第11–20条,NTH_VALUE 是错的工具——它会给每一行都填同一个第N值,而不是筛选出行。这时候该用 OFFSET 10 LIMIT 10(标准SQL)或数据库特有语法(如 SQL Server 的 OFFSET-FETCH)。
-
NTH_VALUE适合:在每行上标注“全局第N大值是多少”,用于对比、差值计算等 - 不适合:筛选、分页、去重取Top N——此时
ROW_NUMBER()+WHERE或LIMIT/OFFSET更直接 - 性能注意:
UNBOUNDED FOLLOWING帧会让优化器无法流式处理,大数据量时可能比子查询还慢
真正难的不是写对语法,而是想清楚你要的是“每行带一个参考值”,还是“只取某一行”。混淆这两者,NTH_VALUE 就会变成陷阱。

















