如何在订购后限制Oracle查询返回的行数?

Mat*_*tin 966 sql oracle pagination limit

有没有办法让Oracle查询表现得像包含一个MySQL limit子句?

MySQL,我可以这样做:

select * 
from sometable
order by name
limit 20,10
Run Code Online (Sandbox Code Playgroud)

获得第21行到第30行(跳过前20行,给出下一行10).在行之后选择行order by,因此它实际上按字母顺序从第20个名称开始.

Oracle,人们提到的唯一的事情是rownum伪列,但它之前 进行了评估order by,这意味着:

select * 
from sometable
where rownum <= 10
order by name
Run Code Online (Sandbox Code Playgroud)

将返回按名称排序的十行的随机集合,这通常不是我想要的.它也不允许指定偏移量.

Kos*_*801 770

您可以使用子查询

select *
from  
( select * 
  from emp 
  order by sal desc ) 
where ROWNUM <= 5;
Run Code Online (Sandbox Code Playgroud)

还可以查看主题On ROWNUM并在Oracle/AskTom上限制结果以获取更多信息.

更新:为了限制下限和上限的结果,事情会变得更加臃肿

select * from 
( select a.*, ROWNUM rnum from 
  ( <your_query_goes_here, with order by> ) a 
  where ROWNUM <= :MAX_ROW_TO_FETCH )
where rnum  >= :MIN_ROW_TO_FETCH;
Run Code Online (Sandbox Code Playgroud)

(从指定的AskTom文章复制)

更新2:从Oracle 12c(12.1)开始,有一种语法可用于限制行或从偏移量开始.

SELECT * 
FROM   sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
Run Code Online (Sandbox Code Playgroud)

有关更多示例,请参阅此答案.感谢Krumia的暗示.

  • AskTom文章也有一个优化器提示,它使用SELECT/*+ FIRST_ROWS(n)*/a.*,rownum rnum结束斜杠前面应该有一个星号.所以正在擦洗它. (6认同)
  • 这绝对是这样做的方法,但要注意(正如问题汤姆文章所说),查询性能随着最大rownum的增加而降低.对于只想查看前几页的查询结果,这是一个很好的解决方案,但如果您使用此作为代码遍历整个表的机制,那么最好重构代码 (3认同)
  • Leigh Riffel 的“只有一个嵌套查询的分析解决方案”就是其中之一。 (2认同)

sam*_*ris 535

从Oracle 12C R1(12.1)开始,那里一个行限制性条款.它不使用熟悉的LIMIT语法,但它可以通过更多选项更好地完成工作.您可以在此处找到完整语法.

要回答原始问题,这是查询:

SELECT * 
FROM   sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
Run Code Online (Sandbox Code Playgroud)

(对于早期的Oracle版本,请参阅此问题中的其他答案)


例子:

以下示例引用链接页面,以防止链接腐烂.

建立

CREATE TABLE rownum_order_test (
  val  NUMBER
);

INSERT ALL
  INTO rownum_order_test
SELECT level
FROM   dual
CONNECT BY level <= 10;

COMMIT;
Run Code Online (Sandbox Code Playgroud)

桌子上有什么?

SELECT val
FROM   rownum_order_test
ORDER BY val;

       VAL
----------
         1
         1
         2
         2
         3
         3
         4
         4
         5
         5
         6
         6
         7
         7
         8
         8
         9
         9
        10
        10

20 rows selected.
Run Code Online (Sandbox Code Playgroud)

获取第一N

SELECT val
FROM   rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS ONLY;

       VAL
----------
        10
        10
         9
         9
         8

5 rows selected.
Run Code Online (Sandbox Code Playgroud)

获得第一N行中,如果N行有关系,让所有的束缚行

SELECT val
FROM   rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS WITH TIES;

       VAL
----------
        10
        10
         9
         9
         8
         8

6 rows selected.
Run Code Online (Sandbox Code Playgroud)

排名最高x的行数

SELECT val
FROM   rownum_order_test
ORDER BY val
FETCH FIRST 20 PERCENT ROWS ONLY;

       VAL
----------
         1
         1
         2
         2

4 rows selected.
Run Code Online (Sandbox Code Playgroud)

使用偏移量,对分页非常有用

SELECT val
FROM   rownum_order_test
ORDER BY val
OFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY;

       VAL
----------
         3
         3
         4
         4

4 rows selected.
Run Code Online (Sandbox Code Playgroud)

您可以将偏移量与百分比组合

SELECT val
FROM   rownum_order_test
ORDER BY val
OFFSET 4 ROWS FETCH NEXT 20 PERCENT ROWS ONLY;

       VAL
----------
         3
         3
         4
         4

4 rows selected.
Run Code Online (Sandbox Code Playgroud)

  • **[我们需要关键字分页的工具支持](http://use-the-index-luke.com/no-offset)** (6认同)
  • @Pra_A 11G 中没有对“LIMIT”/“OFFSET”的本机支持。如果您检查其他答案,他们都以一种方式或其他方式实际实现了限制和偏移量。 (3认同)
  • 只是扩展一下:“OFFSET FETCH”语法是一个语法糖。[详情](/sf/answers/4028327901/) (2认同)
  • Oracle 11G 中如何获取 LIMIT 和 OFFSET ? (2认同)

zel*_*ldi 179

我对以下方法进行了一些性能测试:

Asktom

select * from (
  select a.*, ROWNUM rnum from (
    <select statement with order by clause>
  ) a where rownum <= MAX_ROW
) where rnum >= MIN_ROW
Run Code Online (Sandbox Code Playgroud)

分析

select * from (
  <select statement with order by clause>
) where myrow between MIN_ROW and MAX_ROW
Run Code Online (Sandbox Code Playgroud)

简短的选择

select * from (
  select statement, rownum as RN with order by clause
) where a.rn >= MIN_ROW and a.rn <= MAX_ROW
Run Code Online (Sandbox Code Playgroud)

结果

表有1000万条记录,排序在未编入索引的日期时间行:

  • 解释计划显示所有三个选择的相同值(323168)
  • 但获胜者是AskTom(分析紧随其后)

选择前10行:

  • AskTom:28-30秒
  • 分析:33-37秒
  • 短期替代方案:110-140秒

选择100,000到100,010之间的行:

  • AskTom:60秒
  • 分析:100秒

选择9,000,000到9,000,010之间的行:

  • AskTom:130秒
  • 分析:150秒

  • 我运行了一些快速测试并获得了类似的12c结果.新的`offset`语法与分析方法具有相同的计划和性能. (5认同)
  • @MathieuLongtin`BETWEEN`只是`> = AND <=`的简写(http://stackoverflow.com/questions/4809083/between-clause-versus-and) (3认同)

Lei*_*fel 53

只有一个嵌套查询的分析解决方案:

SELECT * FROM
(
   SELECT t.*, Row_Number() OVER (ORDER BY name) MyRow FROM sometable t
) 
WHERE MyRow BETWEEN 10 AND 20;
Run Code Online (Sandbox Code Playgroud)

Rank()Row_Number()如果名称有重复值,则可以替换但可能会返回比预期更多的记录.

  • 我喜欢分析.您可能想澄清Rank()和Row_Number()之间的行为差​​异. (2认同)

bel*_*daz 29

在Oracle 12c上(参见SQL参考中的行限制子句):

SELECT * 
FROM sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
Run Code Online (Sandbox Code Playgroud)

  • 当然,到目前为止,他们必须使用与其他人完全不同的语法 (50认同)
  • @Derek:实际上,我刚在PostgreSQL手册中注意到这句话http://www.postgresql.org/docs/9.0/static/sql-select.html#AEN69535"条款LIMIT和OFFSET是PostgreSQL特有的语法,也是MySQL使用.SQL:2008标准引入了OFFSET ... FETCH {FIRST | NEXT} ......用于相同的功能".因此,LIMIT从未成为标准的一部分. (10认同)
  • 很明显,在与所有其他供应商坐下来同意SQL中的"LIMIT":2008年之后,他们不得不从微软的书中剔除并打破标准. (9认同)
  • 它疯了.为什么不坚持标准! (4认同)
  • @Derek:是的,不遵守标准是令人遗憾的.但是12cR1中新引入的功能比`LIMIT n,m`更强大(参见我的回答).然后,Oracle应该实现`LIMIT n,m`作为语法糖,因为它相当于'OFFSET n ROWS FETCH NEXT m ROWS ONLY`. (3认同)

Vla*_*cea 25

SQL标准

由于版本 12c Oracle 支持 SQL:2008 标准,它提供以下语法来限制 SQL 结果集:

SELECT
    title
FROM
    post
ORDER BY
    id DESC
FETCH FIRST 50 ROWS ONLY
Run Code Online (Sandbox Code Playgroud)

Oracle 11g 及更早版本

在版本 12c 之前,要获取Top-N记录,您必须使用派生表和ROWNUM伪列:

SELECT *
FROM (
    SELECT
        title
    FROM
        post
    ORDER BY
        id DESC
)
WHERE ROWNUM <= 50
Run Code Online (Sandbox Code Playgroud)


小智 14

在Oracle中,带有排序的分页查询非常棘手.

Oracle提供了一个ROWNUM伪列,它返回一个数字,指示数据库从一个表或一组连接视图中选择行的顺序.

ROWNUM是一个伪列,让许多人陷入困境.ROWNUM值不会永久分配给一行(这是一种常见的误解).实际分配ROWNUM值时可能会造成混淆.在通过查询的筛选谓词在查询聚合或排序之前,将 ROWNUM值分配给行.

更重要的是,ROWNUM值仅在分配后才会递增.

这就是followin查询不返回任何行的原因:

 select * 
 from (select *
       from some_table
       order by some_column)
 where ROWNUM <= 4 and ROWNUM > 1; 
Run Code Online (Sandbox Code Playgroud)

查询结果的第一行不传递ROWNUM> 1谓词,因此ROWNUM不会增加到2.因此,没有ROWNUM值大于1,因此查询不返回任何行.

正确定义的查询应如下所示:

select *
from (select *, ROWNUM rnum
      from (select *
            from skijump_results
            order by points)
      where ROWNUM <= 4)
where rnum > 1; 
Run Code Online (Sandbox Code Playgroud)

在我关于Vertabelo博客的文章中了解有关分页查询的更多信息:

  • *查询结果的第一行未通过ROWNUM&gt; 1谓词(…)* –解释此问题的投票。 (2认同)

Luk*_*zda 11

作为公认答案的扩展,Oracle 在内部使用ROW_NUMBER/RANK函数。OFFSET FETCH语法是一种语法糖。

可以通过使用DBMS_UTILITY.EXPAND_SQL_TEXT程序来观察:

准备样品:

CREATE TABLE rownum_order_test (
  val  NUMBER
);

INSERT ALL
  INTO rownum_order_test
SELECT level
FROM   dual
CONNECT BY level <= 10;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

询问:

SELECT val
FROM   rownum_order_test
ORDER BY val DESC
FETCH FIRST 5 ROWS ONLY;
Run Code Online (Sandbox Code Playgroud)

是常规的:

SELECT "A1"."VAL" "VAL" 
FROM  (SELECT "A2"."VAL" "VAL","A2"."VAL" "rowlimit_$_0",
               ROW_NUMBER() OVER ( ORDER BY "A2"."VAL" DESC ) "rowlimit_$$_rownumber" 
      FROM "ROWNUM_ORDER_TEST" "A2") "A1" 
WHERE "A1"."rowlimit_$$_rownumber"<=5 ORDER BY "A1"."rowlimit_$_0" DESC;
Run Code Online (Sandbox Code Playgroud)

db<>小提琴演示

获取扩展的 SQL 文本:

declare
  x VARCHAR2(1000);
begin
 dbms_utility.expand_sql_text(
        input_sql_text => '
          SELECT val
          FROM   rownum_order_test
          ORDER BY val DESC
          FETCH FIRST 5 ROWS ONLY',
        output_sql_text => x);

  dbms_output.put_line(x);
end;
/
Run Code Online (Sandbox Code Playgroud)

WITH TIES扩展为RANK

declare
  x VARCHAR2(1000);
begin
 dbms_utility.expand_sql_text(
        input_sql_text => '
          SELECT val
          FROM   rownum_order_test
          ORDER BY val DESC
          FETCH FIRST 5 ROWS WITH TIES',
        output_sql_text => x);

  dbms_output.put_line(x);
end;
/

SELECT "A1"."VAL" "VAL" 
FROM  (SELECT "A2"."VAL" "VAL","A2"."VAL" "rowlimit_$_0",
              RANK() OVER ( ORDER BY "A2"."VAL" DESC ) "rowlimit_$$_rank" 
       FROM "ROWNUM_ORDER_TEST" "A2") "A1" 
WHERE "A1"."rowlimit_$$_rank"<=5 ORDER BY "A1"."rowlimit_$_0" DESC
Run Code Online (Sandbox Code Playgroud)

和偏移:

declare
  x VARCHAR2(1000);
begin
 dbms_utility.expand_sql_text(
        input_sql_text => '
          SELECT val
FROM   rownum_order_test
ORDER BY val
OFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY',
        output_sql_text => x);

  dbms_output.put_line(x);
end;
/


SELECT "A1"."VAL" "VAL" 
FROM  (SELECT "A2"."VAL" "VAL","A2"."VAL" "rowlimit_$_0",
             ROW_NUMBER() OVER ( ORDER BY "A2"."VAL") "rowlimit_$$_rownumber" 
       FROM "ROWNUM_ORDER_TEST" "A2") "A1" 
       WHERE "A1"."rowlimit_$$_rownumber"<=CASE  WHEN (4>=0) THEN FLOOR(TO_NUMBER(4)) 
             ELSE 0 END +4 AND "A1"."rowlimit_$$_rownumber">4 
ORDER BY "A1"."rowlimit_$_0"
Run Code Online (Sandbox Code Playgroud)


HMS*_*HMS 9

对于 21c 版本,您可以简单地应用限制,如下所示:

select * from course where ROWNUM <=10;
Run Code Online (Sandbox Code Playgroud)

  • 阅读我的问题的第二部分。这是行不通的,而且它的存在时间比 21c 还要长得多 (3认同)

Fel*_*oni 5

少SELECT语句.此外,性能消耗较少.致记:anibal@upf.br

SELECT *
    FROM   (SELECT t.*,
                   rownum AS rn
            FROM   shhospede t) a
    WHERE  a.rn >= in_first
    AND    a.rn <= in_first;
Run Code Online (Sandbox Code Playgroud)

  • 此外,这是完全错误的答案。问题是限制排序后。因此rownum应该不在子查询中。 (2认同)