用KEEP而非ROW_NUMBER()因语义清晰、执行可控、避免嵌套;KEEP需配合MAX/MIN,通过DENSE_RANK FIRST/LAST取排序首尾行对应列值,不改变分组粒度。

为什么用 KEEP 而不是 ROW_NUMBER()?
因为你要的是「每组内按某字段排序后取某个列的值」,不是返回整行。用 ROW_NUMBER() + 子查询虽然能做,但多一层嵌套、可读性差、且 Oracle 优化器有时无法下推过滤条件。而 KEEP 是 Oracle 原生聚合语法,语义清晰、执行计划更可控,尤其适合在物化视图或复杂报表 SQL 中稳定取值。
典型场景:查每个部门工资最高的员工姓名,但只取名字字段(不关心其他列)。
-
KEEP必须配合MAX或MIN使用,本质是“在满足排序条件的极值行中,保留对应列的值” - 它不改变分组粒度,不会引入额外
GROUP BY列依赖 - 不能单独用,必须写成
MAX(col) KEEP (DENSE_RANK FIRST ORDER BY ...)这种结构
KEEP 的两种写法区别在哪?
DENSE_RANK FIRST 和 DENSE_RANK LAST 控制取「排序后最前」还是「最后」的那条;MAX / MIN 只是语法占位符,实际不参与计算——这点最容易误解。
- 要取每组排序后第一条对应的
name:用MAX(name) KEEP (DENSE_RANK FIRST ORDER BY salary DESC) - 要取最后一条(比如最新时间戳对应的状态):用
MIN(status) KEEP (DENSE_RANK LAST ORDER BY update_time) - 如果排序字段有重复值,
DENSE_RANK会把并列的都算进去,可能返回多行聚合结果(Oracle 允许,但业务上通常需加唯一约束或用RANK配合ROWNUM = 1补救)
常见错误:ORA-00979: not a GROUP BY expression
这个错不是因为你漏写了 GROUP BY,而是你在 SELECT 里混用了非聚合列和 KEEP,或者 KEEP 内部的 ORDER BY 字段没出现在 GROUP BY 中(虽然 Oracle 允许部分省略,但建议显式写出)。
- 正确写法:
SELECT dept_id, MAX(name) KEEP (DENSE_RANK FIRST ORDER BY salary DESC) AS top_name FROM emp GROUP BY dept_id - 错误写法:
SELECT dept_id, name, MAX(salary) KEEP (...) ...——name未聚合也未出现在GROUP BY,直接报错 - 如果
ORDER BY用的是表达式(如TO_CHAR(hire_date, 'YYYY')),确保该表达式也在GROUP BY里,或改用函数索引避免重复计算
性能注意点:KEEP 会走全表扫描吗?
不一定。Oracle 会对 KEEP 内部的 ORDER BY 字段尝试使用索引,尤其是当它和 GROUP BY 字段组合构成前导列时。但别指望它自动利用 salary 索引加速 MAX(name) KEEP (ORDER BY salary DESC) —— 因为 name 和 salary 不在同一个索引里,仍可能回表。
- 最佳实践:建联合索引,如
CREATE INDEX idx_dept_sal_name ON emp(dept_id, salary DESC, name) - 如果数据量大且只查 Top 1,考虑用
MATCH_RECOGNIZE(12c+)替代,它对窗口内排序更高效 -
KEEP在并行查询中表现稳定,但要注意DENSE_RANK的并行一致性,避免不同 slave 对相同排序值分配不同 rank
真正容易被忽略的是:当你用 KEEP 取一个 CLOB 或长字符串字段时,Oracle 默认只返回前 4000 字节(如果数据库字符集是 AL32UTF8,实际可能更少),且不报错——得靠 DBMS_LOB.SUBSTR 显式截断或改用其他方式处理。


















