当前位置:首页 > 问答 > 正文

Oracle里那个rownum到底是啥,怎么用,有啥坑总结一下

Oracle里的rownum,说白了就是Oracle给查询结果每一行临时加上去的一个“伪列”,这个号码是从1开始编的,你可以把它想象成Excel表格最左边那个行号,Oracle在把你查出来的数据结果集摆到你面前的时候,顺手给每一行贴了个序号标签。

rownum是怎么工作的?

它的核心工作机制就三条,理解了这三条,就理解了它所有“古怪”行为的根源:

  1. 从1开始:rownum的赋值永远是从1开始的。
  2. 按顺序递增:Oracle在产生结果集的过程中,每选出一条符合条件的记录,才会给这条记录分配一个rownum,并且这个号码是递增的,1,2,3...这样下去。
  3. 有条件的分配:这是最关键也最让人困惑的一点。rownum是在数据被“选中”的过程中赋值的,而不是在最终的结果集形成之后才统一赋值的。

rownum的基本用法

最常见的用法就是用来限制返回的行数,实现分页或者取前N条记录。

  • 取前10条记录

    SELECT * FROM employees WHERE rownum <= 10;

    这个查询很好理解:Oracle一边从employees表里拿数据,一边给拿出来的数据编号(1,2,3...),一旦编到10号,条件rownum <= 10就不满足了,它就停止搜索,把前10条返回给你。

  • 和ORDER BY一起用(这里开始有坑了)

    SELECT * FROM employees WHERE rownum <= 10 ORDER BY salary DESC;

    你可能会以为这是取“工资最高的前10个人”,但错了,实际执行顺序是:Oracle先随便取出10条记录(比如按它在磁盘上存储的顺序),给这10条记录分配rownum(1到10),然后才对这小小的10条记录进行排序,你得到的只是“随机”10个员工按工资排序的结果,而不是全公司排序后的前10名。

    Oracle里那个rownum到底是啥,怎么用,有啥坑总结一下

rownum的那些“坑”总结

正是因为rownum那个“有条件分配”的工作机制,导致了一些反直觉的情况。

  1. “坑”一:rownum = 1 可以,但 rownum = 2rownum > 1 永远没结果 这是最经典的坑,为什么?

    • 当你写 rownum = 1:Oracle取出第一条数据,分配rownum=1,条件满足,这条记录被保留。
    • 当你写 rownum = 2:Oracle取出第一条数据,分配rownum=1,条件rownum=2不满足(现在是1),所以这条记录被丢弃,然后它取第二条数据,注意,因为第一条被丢弃了,这第二条数据就变成了新的“第一条”,所以Oracle又给它分配了rownum=1!条件rownum=2依然不满足,又被丢弃……如此循环,没有任何一条记录能拿到rownum=2这个号码,所以结果永远是空的。
    • 同理,rownum > 1 也一样,第一条数据拿到rownum=1,不满足>1,丢弃;第二条数据又变成新的第一条,拿到的还是1……永远没有记录能满足条件。
  2. “坑”二:想要正确排序后取前N条,必须使用子查询 这就是为了解决上面提到的“排序失效”问题,正确的做法是分两步走:

    SELECT *
    FROM (
        SELECT * FROM employees ORDER BY salary DESC
    )
    WHERE rownum <= 10;

    在这个语句里,最内层的子查询先执行,它会对全体员工按工资降序排序,生成一个“临时”的、排好序的结果集,外层的查询再从这个已经排好序的结果集里,从第一条开始取,取10条,这样你得到的才是真正工资最高的前10个人。

    Oracle里那个rownum到底是啥,怎么用,有啥坑总结一下

  3. “坑”三:分页查询的写法更复杂 如果你想实现类似“每页10条,取第2页(即第11到20条)”的效果,不能直接写 rownum between 11 and 20(因为between包含等于,会掉进坑一的陷阱),标准的写法是:

    SELECT *
    FROM (
        SELECT t.*, rownum AS rn  -- 给内层查询的rownum起个别名保存下来
        FROM (
            SELECT * FROM employees ORDER BY salary DESC  -- 先排序
        ) t
        WHERE rownum <= 20  -- 控制上限:我要前20条
    )
    WHERE rn > 10;  -- 再过滤:从第11条开始

    这里用了两层子查询,最内层负责排序;中间一层在排序的基础上,用rownum <= 20取出前20条,并把rownum用别名rn固定下来(这是一个关键技巧,一旦有了别名,它就不再是“伪列”,而是一个普通的列值);最外层再基于这个固定的rn值来作大于判断(rn > 10),这样就绕开了rownum不能直接做大于比较的限制。

  4. “坑”四:数据不是“绝对有序”的 即使你没有用ORDER BY,只是简单写SELECT * FROM table WHERE rownum <= 10,这10条记录的顺序也不是绝对的,它通常取决于Oracle的数据读取路径(比如全表扫描的顺序),这个顺序可能和插入顺序有关,但不能被保证,只要查询没有明确的ORDER BY,结果的顺序就应该被视为是随机的。

总结一下

rownum是Oracle一个简单但机制独特的特性,它的核心是“在过程中按顺序分配号码”,用它来做“取前N条”非常方便,但一旦涉及到排序后的前N条或者分页,就必须借助子查询来“固化”行号或结果集顺序,牢记 rownum > Nrownum = N (N>1) 会返回空结果这个特性,就能避免大部分常见的错误。

(主要参考了Oracle官方文档中关于ROWNUM伪列的说明,以及大量Oracle开发者和DBA在技术社区如OTN、Oracle-Base等分享的经验总结。)