KEEP必须与MAX/MIN配合使用,不可单独存在;其本质是修饰聚合函数的语法后缀,ORDER BY仅支持原始列名或常量,不支持表达式或别名,且仅MAX/MIN语义清晰安全。

KEEP必须和MAX/MIN一起用,不能单独写
直接写 col KEEP (DENSE_RANK FIRST ORDER BY sort_col) 会报 ORA-00937: not a single-group group function。KEEP不是独立语法,它只是修饰聚合函数的“后缀”,必须包裹在 MAX() 或 MIN() 里——哪怕你根本不在乎最大值或最小值,只想要排序后的首行对应字段值。
常见误写:
-
SELECT dept_id, name KEEP (DENSE_RANK FIRST ORDER BY salary DESC) FROM emp GROUP BY dept_id→ 错,name不是聚合表达式 -
SELECT dept_id, MAX(name) KEEP (DENSE_RANK FIRST ORDER BY salary DESC) FROM emp GROUP BY dept_id→ 对,MAX()是必需的容器
注意:MAX() 和 MIN() 在这里不参与数值比较,仅起语法占位作用;真正生效的是 KEEP 子句里的排序逻辑。
ORDER BY 里不能写表达式或别名
在 KEEP (DENSE_RANK FIRST ORDER BY ...) 中,ORDER BY 后只能跟原始列名或常量,不能写 LENGTH(name)、TO_DATE(date_str, 'YYYYMMDD') 或 SELECT 列表中的别名,否则触发 ORA-01747: invalid user.table.column。
解决办法是把计算提前:
- 用 CTE 预先算好排序字段:
WITH t AS (SELECT name, dept_id, LENGTH(name) len FROM emp) SELECT dept_id, MAX(name) KEEP (DENSE_RANK FIRST ORDER BY len) FROM t GROUP BY dept_id - 或建虚拟列(Oracle 11g+):
ALTER TABLE emp ADD (name_len GENERATED ALWAYS AS (LENGTH(name)) VIRTUAL),再在 KEEP 中直接引用name_len
KEEP vs ROW_NUMBER() OVER:语义不同,别硬替
想用 KEEP 替代窗口函数,先确认你要的是什么:
- 要「每组中按某字段排序后,取某个列的聚合值(如所有并列第一行中名字的最大值)」→
MAX(name) KEEP (DENSE_RANK FIRST ORDER BY salary DESC)合适 - 要「严格取排序后第 1 行的完整记录(哪怕有并列也只取一条)」→ 必须用
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC, id DESC)+ 外层过滤
二者执行路径差异明显:KEEP 会全量计算组内 DENSE_RANK,再对所有 rank=1 的行做一次聚合;而 ROW_NUMBER() 只标序号,不聚合。业务上混淆这两者,结果可能多出或少掉数据。
哪些聚合函数不能和KEEP搭配
COUNT()、AVG()、SUM() 看似能用,但极易误用:
-
COUNT(*) KEEP (...)→ 报ORA-30496: Argument should be a constant...,COUNT不支持 KEEP 修饰 -
SUM(sal) KEEP (DENSE_RANK LAST ORDER BY hiredate)合法,但它对「最后一批入职员工」求和,不是「最后一个人的 sal」——这点业务方常误解 -
AVG()同理,返回的是满足排序条件那批行的平均值,不是单行值
真正安全、语义清晰的组合只有 MAX() 和 MIN(),且仅用于提取排序极值行对应字段的「代表值」。
KEEP 的坑不在语法难记,而在它表面像取“首行”,实际干的是“对首排名行集做聚合”。没意识到这个本质,就容易写出逻辑正确但结果意外的 SQL。


















