row_number()需配合窗口排序取每组最新记录:按时间等字段desc排序后取rn=1;不可与group by混用,因语义冲突;并列时应选rank()或dense_rank();索引需匹配partition by和order by顺序。

ROW_NUMBER() 本身不“查最新记录”,它只是按指定顺序给行编号。要拿到分组内的最新记录,关键在 ORDER BY 子句怎么写 —— 必须用能体现“新旧”的字段(比如时间戳、版本号、自增ID)倒序排,再筛出编号为 1 的行。
为什么不能直接用 ROW_NUMBER() + GROUP BY?
ROW_NUMBER() 是分析函数,和 GROUP BY 语义冲突:
- GROUP BY 把多行聚合成一行,输出聚合结果;
- ROW_NUMBER() 在未聚合的原始行上逐行编号,必须配合窗口定义(OVER)使用;
- 混用会报错 ORA-00979: not a GROUP BY expression。
常见错误写法:
SELECT deptno, MAX(sal), ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) FROM emp GROUP BY deptno;
这行不通 —— ROW_NUMBER() 需作用于明细行,不是聚合后的一行。
正确做法:子查询 + ROW_NUMBER() 筛 top 1
核心逻辑是:先用ROW_NUMBER() 给每组内行排序编号,再在外层过滤 rn = 1。
假设表 orders 有字段 customer_id、order_date、amount,要查每个客户最新一单:
- 按
customer_id分组,用order_date DESC排序(确保最新在前) -
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC)生成rn - 外层
WHERE rn = 1取每组第一行
完整 SQL:
SELECT customer_id, order_date, amount
FROM (
SELECT customer_id, order_date, amount,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rn
FROM orders
)
WHERE rn = 1;
注意 NULL 和并列情况:用 RANK() 还是 DENSE_RANK()?
如果多个记录的排序字段值相同(比如同一天有多笔订单),ROW_NUMBER() 会强制分配不同编号(1,2,3…),哪怕业务上它们“一样新”。
这时若想保留所有并列最新记录,不能用 ROW_NUMBER(),而该用:
-
RANK() OVER (PARTITION BY ... ORDER BY ... DESC):并列时跳号(如 1,1,3) -
DENSE_RANK() OVER (PARTITION BY ... ORDER BY ... DESC):并列时不跳号(如 1,1,2)
例如查每个部门薪资最高的员工,若允许多人并列,则:
SELECT deptno, empno, sal
FROM (
SELECT deptno, empno, sal,
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rnk
FROM emp
)
WHERE rnk = 1;
性能与索引建议
ROW_NUMBER() 的排序开销直接取决于 OVER 中的 ORDER BY 字段是否走索引:
- 如果 PARTITION BY deptno ORDER BY order_date DESC,理想索引是 (deptno, order_date DESC);
- 复合索引顺序必须匹配窗口定义中的字段顺序;
- 若排序字段是表达式(如 TRUNC(create_time)),需建函数索引;
- Oracle 21c 支持 WINDOW 子句复用,但对单个 ROW_NUMBER() 查询帮助有限,重点还是索引设计。
真正容易被忽略的是:分组键和排序字段的数据分布。如果某组数据量极大(比如一个超级活跃客户有 50 万条订单),即使加了索引,ROW_NUMBER() 仍需扫描全部行来编号 —— 此时应考虑是否真需要“绝对最新”,或能否用时间范围预过滤(WHERE order_date > SYSDATE - 30)再套窗口函数。











