keep子句必须配合max、min等聚合函数使用,不能单独出现;其作用是在分组或窗口中按指定排序取首/尾值,且仅支持dense_rank,方向由order by决定。

KEEP子句必须配合聚合函数使用,不能单独出现
很多人写 SELECT deptno, sal KEEP (DENSE_RANK FIRST ORDER BY sal DESC) 直接报错,因为 KEEP 不是独立函数,它只是聚合函数(如 MAX、MIN、AVG、COUNT)的修饰子句。正确写法必须带聚合函数外壳,比如:MAX(sal) KEEP (DENSE_RANK FIRST ORDER BY sal DESC)。
常见错误现象:ORA-00937: not a single-group group function,往往是因为漏写了外层聚合函数,或在没 GROUP BY 的情况下用了 KEEP。
-
KEEP只能在分组查询(含GROUP BY)或窗口函数中使用,不能用于普通行级表达式 - 外层聚合函数决定返回值类型:用
MAX()就返回数值,用LISTAGG()(Oracle 12c+)可拼字符串 - 如果想取多列(如最高薪员工的姓名和薪资),需对每列分别写一次
KEEP,不能“一次性取整行”
DENSE_RANK FIRST vs LAST:顺序方向决定极值归属
KEEP (DENSE_RANK FIRST ORDER BY ...) 拿的是排序后最靠前的记录对应值,LAST 拿最靠后。注意:这里的“FIRST/LAST”指排序结果的位置,不是原始数据顺序。
例如按 sal DESC 排序后,最高薪排第 1 位 → FIRST 拿到它;最低薪排最后 → LAST 拿到它。但如果写成 ORDER BY sal ASC,那 FIRST 反而拿到最低薪。
- 别只看关键词
FIRST就默认是“最大值”,关键看ORDER BY方向 -
DENSE_RANK处理并列时更稳妥:多个相同最高薪,FIRST会任取其一(非确定性),但不会跳过后续排名 - 不用
RANK或ROW_NUMBER—— Oracle 的KEEP只支持DENSE_RANK
取最高薪员工姓名:字符串字段要配 LISTAGG 或 MAX + KEEP
想查每个部门薪资最高的员工姓名(ename),不能直接 MAX(ename) KEEP (...) ——因为 MAX 对字符串是字典序比较,和薪资无关。必须让 ename 的选取逻辑绑定到 sal 的排序上。
正确做法是用 MAX(ename) KEEP (DENSE_RANK FIRST ORDER BY sal DESC):外层 MAX 是占位聚合函数,真正起筛选作用的是 KEEP 子句里的 ORDER BY sal DESC。
SELECT deptno,
MAX(ename) KEEP (DENSE_RANK FIRST ORDER BY sal DESC) AS top_ename,
MAX(sal) KEEP (DENSE_RANK FIRST ORDER BY sal DESC) AS top_sal
FROM emp
GROUP BY deptno;
- 即使多个员工同为最高薪,
MAX(ename)在KEEP下仍只返回其中一个(Oracle 内部任选),不报错也不报多行 - 若需返回所有并列最高薪员工姓名,得换方案:用分析函数
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC)配合LISTAGG -
KEEP不支持NULLS FIRST/LAST,如有sal为 NULL,会被排在DESC序列末尾(即FIRST忽略它们)
性能与兼容性:比分析函数轻量,但 Oracle 专属
KEEP 是纯聚合路径,执行计划通常比等价的 ROW_NUMBER() + 子查询更简洁,尤其在大数据量分组时减少中间结果集。但它只存在于 Oracle,迁移到 PostgreSQL/MySQL 就得重写。
- 在
GROUP BY场景下,KEEP一般比嵌套分析函数快 10%–30%,因避免了窗口计算开销 - Oracle 8i 引入,所有现代版本都支持,但低版本(如 9i)不支持
LISTAGG配合KEEP - 如果业务需要跨数据库兼容,建议优先用标准 SQL:子查询 +
IN或JOIN关联最大值,虽然啰嗦但可控
FIRST/LAST 和 ORDER BY 方向的耦合关系——写完一定要用已知数据验证排序逻辑是否真按你设想的方式生效。











