Oracle FIRST/LAST中KEEP子句的定义及有无该子句的效果解析
Oracle FIRST/LAST聚合语法中KEEP子句详解
KEEP子句的准确定义
- KEEP不属于独立的语法关键字,是Oracle FIRST/LAST聚合函数的原生内置组成部分。很多人检索时找不到单独的官方说明条目,本质是因为官方文档没有将其拆分独立说明,所有规则都归在SQL语言参考的FIRST、LAST函数章节下。
- 它的标准执行逻辑分为两步:
- 按照子句内ORDER BY指定的列,对分组内的全部数据做密集排名(DENSE_RANK),仅保留排名处于第一(FIRST)位置或最后(LAST)位置的行,过滤掉其余无关行
- 对第一步筛选出的目标子集,执行外层指定的聚合计算(如MAX、MIN、SUM、COUNT等)
- 它的核心价值,就是解决聚合计算的取值列、和用来定位目标行的排序列不一致的场景问题——这也是普通聚合语法天生覆盖不了的能力边界。
使用与不使用KEEP子句的效果差异
不使用KEEP子句的效果
常规分组聚合(如直接使用MAX、MIN)的执行逻辑是:对分组内每一个待计算的列,独立计算该列的极值,完全不校验不同列的极值取值是否来自同一行。
以典型的地址档案查询场景为例:需要为每个关联人取截止指定日期,最新生效(startdate最大)的那条地址记录的全部字段。如果不使用KEEP,常见写法是:
SELECT ra.relation_id, MAX(ra.startdate) AS startdate, MAX(ra.address) AS address, MAX(ra.postal_code) AS postal_code, MAX(ra.city) AS city FROM relation_addresses ra WHERE ra.startdate <= TO_DATE(:reference_date, 'yyyy-mm-dd') GROUP BY ra.relation_id
这种写法的天然缺陷是:MAX(ra.startdate) 拿到的最新生效日期,和另外三个字段列各自取的字典序最大值,极有可能不属于同一行——比如最新地址的城市名不是所有历史记录里字典序最大的,最后返回的结果会把不同行的字段拼接起来,完全不是目标的最新地址真实信息。
使用KEEP子句的效果
加上KEEP子句后,整个聚合的执行顺序会被调整为:
- 先按照GROUP BY维度完成分组
- 针对每个分组,按照KEEP子句内ORDER BY指定的排序列(如startdate)完成密集排名,先筛选出排名处于目标位置(如LAST代表取最大排序列值的所有行)的子集
- 再对这个目标子集内的字段,执行外层的聚合计算,保证所有字段的取值都来自排序列定位的目标行集合
对应优化后的写法:
SELECT ra.relation_id, MAX(ra.startdate) AS startdate, MAX(ra.address) KEEP (DENSE_RANK LAST ORDER BY ra.startdate) AS address, MAX(ra.postal_code) KEEP (DENSE_RANK LAST ORDER BY ra.startdate) AS postal_code, MAX(ra.city) KEEP (DENSE_RANK LAST ORDER BY ra.startdate) AS city FROM relation_addresses ra WHERE ra.startdate <= TO_DATE(:reference_date, 'yyyy-mm-dd') GROUP BY ra.relation_id
这种写法返回的地址、邮编、城市字段,全部来自startdate最大的目标行,不会出现多列极值行错位的问题,完全匹配业务查询需求。
补充说明:如果分组内存在多条排序列取值相同的行(比如同一个关联人在同一天录入了两条地址记录),KEEP子句会把所有同排名的行都保留下来,外层的MAX/MIN聚合会在这个同排名行集合内计算最终的取值结果。
内容的提问来源于stack exchange,提问作者mon
相关产品推荐
相关产品推荐

