LAST_VALUE默认不返回分组最后非空值,因其默认窗口帧为CURRENT ROW且不跳过NULL;需显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING并配合IGNORE NULLS子句才能稳定获取每组按排序逻辑的最后一个非空值。

LAST_VALUE 为什么默认不返回分组最后非空值
LAST_VALUE 是窗口函数,但它默认行为是“按 ORDER BY 列的当前行位置取值”,不是“跳过 NULL 取最后一个非空”。如果 ORDER BY 列存在 NULL,或数据本身有空值,LAST_VALUE(col) 很可能直接返回 NULL——哪怕后面还有非空值。关键在于:它不自动过滤 NULL,也不改变窗口帧范围。
常见错误现象:LAST_VALUE(name) OVER (PARTITION BY dept_id ORDER BY hire_date) 在 hire_date 相同或含 NULL 时,结果不稳定;更糟的是,若最后一行 name 是 NULL,整个分组都得 NULL。
- 必须显式指定窗口帧(
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),否则默认是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,导致永远取不到后面的值 -
LAST_VALUE对 NULL 不敏感,要靠IGNORE NULLS(Oracle 10g+ 支持)才能跳过 - ORDER BY 列需能唯一确定逻辑顺序,否则相同值会导致“最后”不可预测
正确写法:用 IGNORE NULLS + 全窗口帧
Oracle 从 10g 起支持 IGNORE NULLS 子句,这是解决该问题的核心。配合显式窗口定义,才能稳定拿到每个分组中按排序逻辑出现的最后一个非空值。
示例:按部门分组,取每个部门中按 hire_date 排序后,最后一个非空的 job_title
SELECT
emp_id,
dept_id,
job_title,
LAST_VALUE(job_title) IGNORE NULLS
OVER (PARTITION BY dept_id ORDER BY hire_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_nonnull_job
FROM employees;
-
IGNORE NULLS必须紧跟在函数名后、括号前,写成LAST_VALUE(job_title) IGNORE NULLS,不能放在 OVER 里 -
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING确保扫描整个分组,否则CURRENT ROW截断会失效 - 如果
hire_date有重复,建议追加唯一列(如emp_id)避免并列排序歧义:ORDER BY hire_date, emp_id
替代方案:当 IGNORE NULLS 不可用(老版本 Oracle)
Oracle 9i 或禁用 IGNORE NULLS 的环境,只能绕行。常用做法是用 ROW_NUMBER() 配合子查询,先筛出每组非空值的最大排序序号,再关联回原表。
等效逻辑(不依赖 IGNORE NULLS):
WITH ranked AS (
SELECT emp_id, dept_id, job_title, hire_date,
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY hire_date DESC, emp_id DESC) rn
FROM employees
WHERE job_title IS NOT NULL
)
SELECT e.*, r.job_title AS last_nonnull_job
FROM employees e
LEFT JOIN ranked r ON e.dept_id = r.dept_id AND r.rn = 1;
- WHERE 过滤掉 NULL 再排序,保证
rn = 1对应的就是该组“最后”的非空记录 - ORDER BY 中
DESC是关键,否则rn = 1拿到的是“第一个”非空值 - 性能上比单层窗口略差,尤其大数据量时需注意
dept_id + hire_date上的索引
容易被忽略的 NULL 边界情况
即使写了 IGNORE NULLS,仍可能返回 NULL——不是 bug,而是语义如此:如果某分组内所有目标列全为 NULL,LAST_VALUE(... IGNORE NULLS) 无值可选,结果就是 NULL。
- 检查是否真有全 NULL 分组:
SELECT dept_id FROM employees GROUP BY dept_id HAVING COUNT(job_title) = 0 - 若业务上不允许 NULL 结果,可在外层用
NVL或COALESCE提供默认值:COALESCE(LAST_VALUE(...) IGNORE NULLS, 'N/A') - 注意字符串比较中的空格:Oracle 中
' '(空格)不等于 NULL,但TRIM(job_title) IS NULL可能漏判,需按实际清洗规则处理
真正麻烦的不是语法,是确认“最后”到底按什么逻辑定义——时间戳?序列号?还是插入顺序?一旦 ORDER BY 列本身不可靠,整个 LAST_VALUE 就失去意义。


















