NTH_VALUE()总返回NULL是因为它必须配合OVER()子句使用,且默认窗口帧仅包含当前行及之前行,若目标位置在当前行之后或分组数据不足N行,即返回NULL;需显式指定ORDER BY和ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING才能正确取值。

为什么NTH_VALUE()总返回NULL?
因为NTH_VALUE()是窗口函数,必须配合OVER()子句使用,且默认窗口帧(frame)是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW——这意味着它只看当前行及之前的数据,如果目标位置在当前行之后(比如要取第3个值,但当前才到第1行),就拿不到,返回NULL。
常见错误写法:SELECT NTH_VALUE(salary, 3) OVER (PARTITION BY dept) FROM emp; —— 没指定排序和完整帧,结果几乎全是NULL。
- 必须用
ORDER BY明确排序逻辑,否则“第N个”无意义 - 必须显式扩展窗口帧,常用
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - 如果分组数据少于N行,
NTH_VALUE()仍返回NULL(这是设计行为,不是bug)
NTH_VALUE()与FIRST_VALUE()/LAST_VALUE()的帧差异
FIRST_VALUE()和LAST_VALUE()在未显式声明帧时有隐含默认:前者等价于UNBOUNDED PRECEDING,后者默认是CURRENT ROW(所以常需手动改成UNBOUNDED FOLLOWING)。而NTH_VALUE()没这种“宽容”,它完全依赖你定义的帧范围。
- 要安全取分组内第N个值,帧必须覆盖整个分组:
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - 排序字段不能有重复值,否则“第3个”可能因排序不稳定而漂移;必要时加二级排序,如
ORDER BY salary DESC, id - PostgreSQL不支持
NTH_VALUE()(截至15.x),MySQL 8.0+、Oracle、SQL Server 2012+ 支持
实际例子:取每个部门薪资第2高的员工姓名
假设表emp(dept, name, salary),目标是为每行标注“本部门第2高薪者姓名”(注意:不是只查出那1个人,而是每行都带这个值):
SELECT
dept,
name,
salary,
NTH_VALUE(name, 2) OVER (
PARTITION BY dept
ORDER BY salary DESC, id
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS second_highest_name
FROM emp;关键点:
-
ORDER BY salary DESC, id确保排序唯一且符合业务预期(同薪时按id定先后) - 缺
id可能导致相同薪资下NTH_VALUE()结果不确定 - 若某部门只有1人,
second_highest_name为NULL,这是正常表现
替代方案:当NTH_VALUE()不可用或逻辑更复杂时
比如目标变成“取每个部门薪资第2高的人,但只返回这一条记录”,这时NTH_VALUE()就不合适了——它天生是逐行计算的窗口函数,不适合过滤整行。
- 改用
ROW_NUMBER()+ 子查询:SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn FROM emp) t WHERE rn = 2 - 注意
ROW_NUMBER()会强制去重排序,RANK()和DENSE_RANK()对并列处理不同,选哪个取决于“第2高”是否允许并列(例如两个最高薪并列第1,则第2高是否存在?) - SQLite、老版本MySQL等不支持窗口函数时,只能用自连接或相关子查询,性能差很多
真正容易被忽略的是:NTH_VALUE()的语义是“把第N个值广播到窗口内所有行”,而不是“筛选出第N行”。用错场景比写错语法更常见。

















