KEEP是Oracle中必须配合聚合函数(如MAX/MIN)和GROUP BY使用的修饰符,用于在分组内按ORDER BY排序后取首/尾组的聚合值;单独使用或缺失聚合函数会报ORA-00978错误。

KEEP子句必须配合GROUP BY和聚合函数一起用
单独写 KEEP 是语法错误,它不是独立语句,而是 MAX()、MIN()、AVG() 等聚合函数的修饰符。常见误写如 SELECT col1 KEEP (DENSE_RANK FIRST ORDER BY col2) FROM t GROUP BY col3 —— 缺少外层聚合函数,Oracle 会直接报错 ORA-00978: nested group function without GROUP BY。
正确写法必须是:先选一个聚合函数(通常用 MAX() 或 MIN()),再在其后加 KEEP 子句。比如获取每组按时间排序的首条记录的姓名:
SELECT dept_id, MAX(emp_name) KEEP (DENSE_RANK FIRST ORDER BY hire_date) AS first_hired_name FROM employees GROUP BY dept_id;
-
KEEP (DENSE_RANK FIRST ...)表示取排序后“排第一”的那批记录(可能多个并列第一)中,聚合函数作用的对象值 - 这里用
MAX(emp_name)是因为emp_name是字符串,需用字符串聚合函数;若字段是数字且想取对应值,同样用MAX()或MIN(),它们在此上下文中不真做大小比较,只是从候选集中选一个 -
DENSE_RANK和RANK在并列时行为一致,但ROW_NUMBER不被KEEP支持 —— 写了会报ORA-30498: invalid argument for KEEP clause
FIRST/LAST 和 DENSE_RANK 的组合决定“首尾”含义
FIRST 和 LAST 不是指物理顺序,而是由 ORDER BY 子句定义的逻辑顺序中的极值位置。比如要取每组薪资最高者的姓名,不能写 MAX(salary),而应写:
SELECT dept_id, MAX(emp_name) KEEP (DENSE_RANK LAST ORDER BY salary) AS top_paid_name FROM employees GROUP BY dept_id;
-
LAST对应ORDER BY salary中的最大值位置;FIRST对应最小值位置 - 如果排序字段有重复(比如两人同为最高薪),
DENSE_RANK LAST会把他们都纳入候选集,外层MAX(emp_name)就在这些人里挑字典序最大的名字 —— 这就是为什么结果未必是你“预期的那个”人 - 若一定要唯一结果,得确保
ORDER BY子句足够细化,例如ORDER BY salary DESC, emp_id ASC,避免并列
性能上,KEEP 比子查询或分析函数更轻量但灵活性低
相比 ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) + 外层过滤,KEEP 不产生中间行集,执行计划通常少一次 SORT AGGREGATE 或 WINDOW SORT,对大表分组有明显优势。
- 但它只能返回单个标量值(如一个名字、一个ID),不能同时取多列(比如首条记录的
emp_name和hire_date)—— 想取多列得写多次KEEP,且每次ORDER BY必须一致,否则语义错乱 - 无法跳过 NULL 值自动找下一个非空项;如果
ORDER BY字段含 NULL,默认按NULLS LAST处理(除非显式写NULLS FIRST),这点容易导致“首条”实际是 NULL 记录 - Oracle 9i 引入,所有现代版本都支持,但某些旧版 JDBC 驱动可能对
KEEP解析异常,遇到SQLException时先确认驱动版本
常见错误:混淆 KEEP 和分析函数的语义边界
看到 “取每组第一条”,下意识写 ROW_NUMBER() = 1 很自然,但换成 KEEP 时容易漏掉关键约束:它只工作于已分组后的聚合上下文,不能用于未聚合的行级筛选。
- 下面写法是错的:
SELECT emp_name KEEP (DENSE_RANK FIRST ORDER BY hire_date) FROM employees WHERE dept_id = 10;
—— 缺少GROUP BY,即使只查一个部门也不行 - 也不能在
HAVING或WHERE里用KEEP表达式,它只允许出现在SELECT列表或ORDER BY中(后者极少用) - 当需要“每个部门最早入职的员工完整信息”时,
KEEP只能帮你拿到其中一列(如名字),其他字段得靠关联回原表,或者直接换用FIRST_VALUE()分析函数
KEEP,得记住它本质是“聚合时附带排序选择”,不是“取记录”。哪个字段要选、按什么排、并列怎么处理——这三件事定清楚了,剩下的就是套模板。否则很容易写出语法合法但结果不对的语句。


















