KEEP子句必须配合聚合函数使用,不能单独出现;它仅是MAX()、MIN()等聚合函数的修饰子句,用于在分组内按指定排序取附带值。

KEEP子句必须配合聚合函数使用,不能单独出现
Oracle 的 KEEP 不是独立语法,它只是 MAX()、MIN()、SUM() 等聚合函数的修饰子句。常见错误是写成 SELECT name KEEP (DENSE_RANK FIRST ORDER BY score DESC) FROM t GROUP BY dept —— 这会报错,因为缺少外层聚合函数。
正确写法必须包裹一层聚合,最常用的是 MAX() 或 MIN()(它们对字符串/数值都安全):
SELECT dept,
MAX(name) KEEP (DENSE_RANK FIRST ORDER BY score DESC) AS top_name,
MAX(score) AS max_score
FROM student
GROUP BY dept;
这里 MAX(name) 本身不参与排序逻辑,真正起作用的是后面的 KEEP 子句:它告诉 Oracle,在每个分组内按 score DESC 排序后取排第一的那些 name,再对这些 name 执行 MAX()(如果只有一行,结果就是那个值;若有并列第一,则取字典序最大者)。
DENSE_RANK FIRST vs LAST:别混淆“最高分对应人”和“最后一条最高分记录”
用 FIRST 是取排序后靠前的记录,LAST 是取靠后的。但注意:FIRST 和 LAST 指的是 ORDER BY 序列中的位置,不是原始插入顺序。
- 要找「每个部门分数最高的人名」→ 用
DENSE_RANK FIRST ORDER BY score DESC - 要找「每个部门分数最低的人名」→ 改为
DENSE_RANK FIRST ORDER BY score ASC(不是LAST) - 若最高分有多人,且你明确要字典序最小的名字 → 用
MIN(name) KEEP (...)而非MAX(name) KEEP (...)
容易踩坑的是以为 LAST 能拿到“最后插入的那条高分记录”,其实它只响应 ORDER BY 结果。原始时序信息在分组后已丢失,除非你在 ORDER BY 中显式加入时间字段,比如 ORDER BY score DESC, create_time DESC。
处理并列情况:DENSE_RANK 和 RANK 的行为差异直接影响结果行数
KEEP 后面必须跟 DENSE_RANK 或 RANK,不能用 ROW_NUMBER。两者的区别在于对相同排序键的处理:
-
DENSE_RANK FIRST:遇到相同score,所有并列者都被纳入候选,最终由外层MAX()或MIN()合并成一个值 -
RANK FIRST:行为类似,但跳过后续排名(如两个并列第1,则下一个是第3),实际结果与DENSE_RANK在单次KEEP中无差别 - 真正影响结果的是外层聚合函数:用
MAX()会返回并列者中字典序最大的名字,用MIN()则返回最小的
如果你需要返回全部并列者(不止一个名字),KEEP 就不适用了——它天生只产出单个标量值。此时得换用分析函数 + ROW_NUMBER() 或 RANK() 配合子查询过滤。
性能和可读性提醒:简单场景够用,复杂逻辑建议改用窗口函数
KEEP 在 Oracle 9i 引入,优势是语法紧凑,适合“取某字段随最值附带值”的简单需求。但它有几个硬限制:
- 无法在
WHERE或HAVING中直接使用(必须出现在SELECT或ORDER BY) - 不能嵌套其他聚合或分析函数
- 调试困难:执行计划里不会展开
KEEP的内部筛选过程,出错时难定位是排序逻辑问题还是聚合函数选错
当需求变成「取每个部门最高分且创建时间最新的那条完整记录」,或者要同时取 top 1 的 name、id、create_time 三个字段时,硬塞多个 KEEP 表达式会让 SQL 变得脆弱且难以维护。这时候更稳妥的做法是用 ROW_NUMBER() OVER (PARTITION BY dept ORDER BY score DESC, create_time DESC) 做标记,再外层 WHERE rn = 1 过滤。
真实生产环境里,宁可多写几行清晰的窗口函数,也不要为了省一两个词去啃 KEEP 的语义边界。


















