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


1031

有没有一种方法可以使Oracle查询的行为像包含MySQL limit子句一样?

在中MySQL,我可以这样做:

select * 
from sometable
order by name
limit 20,10

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

在中Oracle,人们唯一提到的是rownum伪列,但它是在之前 评估的order by,这意味着:

select * 
from sometable
where rownum <= 10
order by name

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


16
在SQL:2008中标准化。
dalle

14
限制是由汤姆·凯特宣布甲骨文12C ...
wolφi

14
获取结果集中的下一页?
Mathieu Longtin

3
@YaroslavShabalin特别是,分页搜索始终使用此模式。几乎任何具有任何搜索功能的应用都将使用它。另一个用例是仅加载长列表或表客户端的一部分,并为用户提供扩展选项。
jpmc26 2014年

3
@YaroslavShabalin除非基础数据由于导致更改,否则您将无法获得其他结果集ORDER BY。这就是订购的全部要点。如果基础数据发生变化,并且您的结果集因此而发生变化,那么为什么不向用户显示更新的结果而不是过时的信息呢?而且,国家管理是一个祸患,应尽可能避免。它一直是复杂性和错误的源头。这就是为什么功能如此流行的原因。而且什么时候知道内存中的整个结果集到期?在网络中,您无法知道用户何时离开。
jpmc26 2014年

Answers:


619

从Oracle 12C R1(12.1)开始,那里一个行限制性条款。它不使用熟悉的LIMIT语法,但是可以通过更多选项更好地完成工作。您可以在此处找到完整的语法。(还可以在此答案中阅读有关Oracle内部如何工作的更多信息)。

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

SELECT * 
FROM   sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

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


例子:

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

设定

CREATE TABLE rownum_order_test (
  val  NUMBER
);

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

COMMIT;

桌子上有什么?

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.

获取第一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.

获得第一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.

x行的前%

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

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

4 rows selected.

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

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.

您可以将偏移量与百分比结合起来

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.


1
只是为了扩展:OFFSET FETCH语法是语法糖。详细信息
Lukasz Szozda

793

您可以像这样使用子查询

select *
from  
( select * 
  from emp 
  order by sal desc ) 
where ROWNUM <= 5;

还可以查看主题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;

(从指定的AskTom文章复制)

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

SELECT * 
FROM   sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

有关更多示例,请参见此答案。感谢Krumia的提示。


5
绝对是这样做的方法,但是请注意(如Ask tom文章所述),随着最大行数的增加,查询性能会下降。这对于查询结果是一个很好的解决方案,在这种情况下,您只希望看到前几页,但是如果您将其用作一种代码来遍历整个表的机制,则最好重构代码
Chris Gill

1
+1您的较低/较高版本实际上帮助我解决了一个问题,其中仅有上限的rownum子句极大地减慢了我的查询速度。
开尔文

1
Leigh Riffel是“只有一个嵌套查询的解析解决方案”。
达伦·希克斯

7
AskTom文章也有使用SELECT / * + FIRST_ROWS(n)/ a的优化程序提示,rownum rnum斜杠前应加星号。所以正在清理它。
大卫·曼

1
请注意,对于Oracle 11,带有ROWNUM的外部SELECT将阻止您调用UpdatableResultSet(带有ORA-01446)上的deleteRow-期待12c R1的更改!
nsandersen,2015年

185

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

Asktom

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

分析型

select * from (
  <select statement with order by clause>
) where myrow between MIN_ROW and MAX_ROW

短替代

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

结果

表有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秒

辛苦了 您是否尝试使用介于而不是> =和<=的简短替代方法?
Mathieu Longtin

4
@MathieuLongtin BETWEEN只是>= AND <=stackoverflow.com/questions/4809083/between-clause-versus-and)的简写
2011年

1
zeldi-这是哪个版本?Oracle在11.1中提高了分析性能。和11.2。
Leigh Riffel 2012年

@Leigh Riffel是10.2.0.5; 有一天,我可能会花一些时间并检查11i版本。
zeldi 2013年

5
我进行了一些快速测试,得出12c的结果相似。新offset语法与分析方法具有相同的计划和性能。
乔恩·海勒2014年

55

仅包含一个嵌套查询的解析解决方案:

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

Rank()Row_Number()如果name值重复,则可以替换它,但返回的记录可能比您期望的多。


3
我喜欢分析。您可能想弄清楚Rank()和Row_Number()之间的行为差​​异是什么。
戴夫·科斯塔

确实,不确定我为什么不考虑重复。因此,在这种情况下,如果name有重复的值,则RANK可以提供比您期望的更多的记录,因此您应该使用Row_Number。
Leigh Riffel

如果提及rank(),也值得注意的是dense_rank(),哪种输出控制可能更有用,因为后者不会“跳过”数字,而rank()可以。无论如何,此问题row_number()最适合。另一个不是这种技术适用于任何支持上述功能的数据库。
Used_By_Already

28

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

SELECT * 
FROM sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

53
当然,到目前为止,他们必须使用与其他所有人完全不同的语法
Mathieu Longtin

9
显然,在与所有其他供应商坐下来就LIMITSQL:2008 达成一致意见之后,他们不得不从Microsoft的书中删除并打破了标准。
beldaz 2013年

1
有趣的是,最近我听说最新的标准包括该语法,因此也许Oracle在实施之前先将其引入。可以说它比LIMIT ... OFFSET
beldaz 2013年

3
@Derek:是的,遗憾的是不遵循标准。但是12cR1中新引入的功能不仅功能强大LIMIT n, m(请参阅我的答案)。再说一次,Oracle应该已经实现LIMIT n, m为语法糖,因为它等效于OFFSET n ROWS FETCH NEXT m ROWS ONLY
sampathsris 2014年

10
@Derek:实际上,我刚刚在PostgreSQL手册postgresql.org/docs/9.0/static/sql-select.html#AEN69535中注意到了这一点 “ LIMIT和OFFSET子句是PostgreSQL特定的语法,也由MySQL使用。SQL :2008标准引入了“ OFFSET ... FETCH {FIRST | NEXT} ...子句以实现相同的功能”。因此,LIMIT绝不是标准的一部分。
贝尔达兹2015年

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; 

查询结果的第一行未传递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; 

Vertabelo博客上的文章中找到有关分页查询的更多信息:


2
查询结果的第一行未通过ROWNUM> 1谓词(…) –赞成对此进行解释。
Piotr Dobrogost '19年

6

SQL标准

正如我在本文中所解释的,SQL:2008标准提供了以下语法来限制SQ​​L结果集:

SELECT
    title
FROM
    post
ORDER BY
    id DESC
FETCH FIRST 50 ROWS ONLY

Oracle 11g和更早版本

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

SELECT *
FROM (
    SELECT
        title
    FROM
        post
    ORDER BY
        id DESC
)
WHERE ROWNUM <= 50

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;

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

5

作为公认答案的扩展,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;

查询:

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

是常规的:

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;

db <> fiddle演示

获取扩展的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;
/

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

和偏移量:

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"

3

如果您不在Oracle 12C上,则可以使用TOP N查询,如下所示。

SELECT *
 FROM
   ( SELECT rownum rnum
          , a.*
       FROM sometable a 
   ORDER BY name
   )
WHERE rnum BETWEEN 10 AND 20;

您甚至可以如下将with子句中的from子句移动

WITH b AS
( SELECT rownum rnum
      , a.* 
   FROM sometable a ORDER BY name
) 
SELECT * FROM b 
WHERE rnum BETWEEN 10 AND 20;

实际上,我们在这里创建一个内联视图,并将rownum重命名为rnum。您可以在主查询中使用rnum作为过滤条件。


1
就我而言,这没有返回正确的行。我要解决的问题是分别进行ORDER BYrownum基本上,我创建了一个具有ORDER BY子句的子查询
帕特里克·格雷戈里奥

否决投票,因为答案不正确。问题是关于排序后的限制,因此rownum应该在子查询之外。
Piotr Dobrogost

@PiotrDobrogost rownum仅在外部。
桑迪

2

我已经开始为Oracle 1z0-047考试做准备,并针对12c进行了验证。在准备该考试时,我遇到了一种12c增强功能,称为“ FETCH FIRST”。它使您能够根据方便提取行/限制行。它有几个选项

- FETCH FIRST n ROWS ONLY
 - OFFSET n ROWS FETCH NEXT N1 ROWS ONLY // leave the n rows and display next N1 rows
 - n % rows via FETCH FIRST N PERCENT ROWS ONLY

例:

Select * from XYZ a
order by a.pqr
FETCH FIRST 10 ROWS ONLY

3
stackoverflow.com/a/26051830/635608-其他答案中已经提供了此内容。请不要发布几个月前已经发布的内容。
Mat

1
哦,可以肯定的是,没有遍历所有答案,我很早就遇到了子查询,请记住这一点。
arjun gaur

1
select * FROM (SELECT 
   ROW_NUMBER() OVER (ORDER BY sal desc),* AS ROWID, 
 FROM EMP ) EMP  where ROWID=5

更大,然后找出价值

select * FROM (SELECT 
       ROW_NUMBER() OVER (ORDER BY sal desc),* AS ROWID, 
     FROM EMP ) EMP  where ROWID>5

更少然后发现价值

select * FROM (SELECT 
       ROW_NUMBER() OVER (ORDER BY sal desc),* AS ROWID, 
     FROM EMP ) EMP  where ROWID=5

ROW_NUMBER()Leigh Riffel已经发布了Downvote作为基于解决方案的解决方案。成瘾的是,显示的代码中有语法错误。
Piotr Dobrogost

1

对于查询返回的每一行,ROWNUM伪列返回一个数字,该数字指示Oracle从表或连接的行集中选择行的顺序。所选的第一行的ROWNUM为1,第二行的为2,依此类推。

  SELECT * FROM sometable1 so
    WHERE so.id IN (
    SELECT so2.id from sometable2 so2
    WHERE ROWNUM <=5
    )
    AND ORDER BY so.somefield AND ROWNUM <= 100 

我已经在oracle服务器上实现了11.2.0.1.0


当问题询问有关限制有序行的问题时,您
投票否决了

@PiotrDobrogost知道这不是一项艰巨的任务,仅限制有所更改的所有rdbms都必须使用排序关键字。
Sumesh TG,

-1

如果是SQL-Developer,它将自动仅获取前50行。如果我们向下滚动,它将获取另外50行,依此类推!

因此,在使用sql-developer工具的情况下,我们无需定义!


-3

在甲骨文

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

VAL值

    10
    10
     9
     9
     8

已选择5行。

SQL>


7
您应该指定从Oracle 12c开始适用,并从某个地方复制/粘贴-请始终引用您的消息来源。
2014年

来源是这个 @Mat。和Rakesh,请至少尝试使答案适应原始问题。我也提供了引用相同来源的答案,但我试图做到全面,并引用了原始来源。
sampathsris 2014年

-4

(未经测试)这样的事情可能会做

WITH
base AS
(
    select *                   -- get the table
    from sometable
    order by name              -- in the desired order
),
twenty AS
(
    select *                   -- get the first 30 rows
    from base
    where rownum < 30
    order by name              -- in the desired order
)
select *                       -- then get rows 21 .. 30
from twenty
where rownum > 20
order by name                  -- in the desired order

还有一个分析功能等级,可用于排序。


2
由于ROWNUM是结果集上的一列,因此不会返回任何行,因此最后一个WHERE条件始终为false。另外,您不能使用ROWNUM和按保证订单的订单。
2013年

2
优秀的。让我们将其留在这里,作为对其他人的警告。
EvilTeach 2014年

-5

与上述相同,但有更正。可行,但绝对不漂亮。

   WITH
    base AS
    (
        select *                   -- get the table
        from sometable
        order by name              -- in the desired order
    ),
    twenty AS
    (
        select *                   -- get the first 30 rows
        from base
        where rownum <= 30
        order by name              -- in the desired order
    )
    select *                       -- then get rows 21 .. 30
    from twenty
    where rownum < 20
    order by name                  -- in the desired order

老实说,最好使用上述答案。


5
这是不正确的,因为WHERE子句是在ORDER BY之前求值的。
2013年

3
有趣的是,我在下面的错误答案中被盗了。
EvilTeach
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.