关于SQL:如何限制Oracle查询在排序后返回的行数?

How do I limit the number of rows returned by an Oracle query after ordering?

有没有办法让它包含在查询的行为样Oraclea MySQL limit条款?

MySQL,我可以这样做:

1
2
3
4
SELECT *
FROM sometable
ORDER BY name
LIMIT 20,10

to get to the 30th(二十一行跳过第一个20一10)。这是order by行选定后,它是在20名alphabetically起飞。

Oracle,唯一使人说是rownum假柱,它被order by之前,这意味着本:

1
2
3
4
SELECT *
FROM sometable
WHERE rownum <= 10
ORDER BY name

要返回一随机序的一组行的名称,这是通常不会是我想要的。它也不允许在指定的偏移。


您可以使用类似这样的子查询

1
2
3
4
5
6
SELECT *
FROM  
( SELECT *
  FROM emp
  ORDER BY sal DESC )
WHERE ROWNUM <= 5;

还可以查看rownum和limiting results在Oracle/asktom上的主题,了解更多信息。

更新:用上界和下界限制结果会让事情变得更加膨胀

1
2
3
4
5
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:从Oracle12c(12.1)开始,有一种语法可以限制行或从偏移量开始。

1
2
3
4
SELECT *
FROM   sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

有关更多示例,请参阅此答案。感谢克鲁米娅的提示。


从Oracle12cR1(12.1)开始,有一个行限制子句。它不使用熟悉的LIMIT语法,但是它可以用更多的选项更好地完成工作。您可以在这里找到完整的语法。

要回答原始问题,请回答以下问题:

1
2
3
4
SELECT *
FROM   sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

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

实例:

下面的例子是从链接页面引用的,以防止链接腐烂。

安装程序

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE rownum_order_test (
  val  NUMBER
);

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

COMMIT;

桌子上有什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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行有联系,得到所有的联系行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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%

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT val
FROM   rownum_order_test
ORDER BY val
FETCH FIRST 20 PERCENT ROWS ONLY;

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

4 ROWS selected.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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.

可以将偏移量与百分比相结合

1
2
3
4
5
6
7
8
9
10
11
12
13
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
2
3
4
5
SELECT * FROM (
  SELECT a.*, ROWNUM rnum FROM (
    <SELECT statement WITH ORDER BY clause>
  ) a WHERE rownum <= MAX_ROW
) WHERE rnum >= MIN_ROW

解析

1
2
3
SELECT * FROM (
  <SELECT statement WITH ORDER BY clause>
) WHERE myrow BETWEEN MIN_ROW AND MAX_ROW

短期替代方案

1
2
3
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秒

选择100000到100010之间的行:

  • asktom:60秒
  • 分析:100秒

选择9000000和9000010之间的行:

  • asktom:130秒
  • 分析:150秒


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

1
2
3
4
5
SELECT * FROM
(
   SELECT t.*, ROW_NUMBER() OVER (ORDER BY name) MyRow FROM sometable t
)
WHERE MyRow BETWEEN 10 AND 20;

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


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

1
2
3
4
SELECT *
FROM sometable
ORDER BY name
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;


在Oracle中,带排序的分页查询非常困难。

Oracle提供一个rownum伪列,该列返回一个数字,指示数据库从一个表或一组联接视图中选择行的顺序。

rownum是一个让很多人陷入麻烦的伪列。rownum值不会永久分配给行(这是一个常见的误解)。当实际分配rownum值时,可能会令人困惑。rownum值在传递查询的筛选谓词之后,但在查询聚合或排序之前分配给行。

更重要的是,rownum值只有在分配后才会递增。

这就是以下查询不返回行的原因:

1
2
3
4
5
 SELECT *
 FROM (SELECT *
       FROM some_table
       ORDER BY some_column)
 WHERE ROWNUM <= 4 AND ROWNUM > 1;

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

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

1
2
3
4
5
6
7
SELECT *
FROM (SELECT *, ROWNUM rnum
      FROM (SELECT *
            FROM skijump_results
            ORDER BY points)
      WHERE ROWNUM <= 4)
WHERE rnum > 1;

有关分页查询的更多信息,请参阅我在Vertabelo博客上的文章:

  • Oracle Rownum解释
  • Top-N和分页查询


更少的select语句。而且,性能消耗更少。学分:[email protected]

1
2
3
4
5
6
SELECT *
    FROM   (SELECT t.*,
                   rownum AS rn
            FROM   shhospede t) a
    WHERE  a.rn >= in_first
    AND    a.rn <= in_first;


1
2
3
SELECT * FROM (SELECT
   ROW_NUMBER() OVER (ORDER BY sal DESC),* AS ROWID,
 FROM EMP ) EMP  WHERE ROWID=5

大于值则发现

1
2
3
SELECT * FROM (SELECT
       ROW_NUMBER() OVER (ORDER BY sal DESC),* AS ROWID,
     FROM EMP ) EMP  WHERE ROWID>5

减去价值,找出

1
2
3
SELECT * FROM (SELECT
       ROW_NUMBER() OVER (ORDER BY sal DESC),* AS ROWID,
     FROM EMP ) EMP  WHERE ROWID=5


如果您不在Oracle12c上,可以使用下面的TopN查询。

1
2
3
4
5
6
7
8
SELECT *
 FROM
   ( SELECT rownum rnum
          , a.*
       FROM sometable a
   ORDER BY name
   )
WHERE rnum BETWEEN 10 AND 20;

您甚至可以将这个从子句移到WITH子句中,如下所示

1
2
3
4
5
6
7
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作为筛选条件。


我开始准备Oracle 1Z0-047考试,根据12C验证在为它做准备时,我遇到了一个12C增强,称为"先取"。它允许您根据自己的方便提取行/限制行。它有几个选项

1
2
3
- 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

例子:

1
2
3
SELECT * FROM XYZ a
ORDER BY a.pqr
FETCH FIRST 10 ROWS ONLY


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

1
2
3
4
5
6
  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中实现了这个功能。


在甲骨文中

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

瓦尔

1
2
3
4
5
    10
    10
     9
     9
     8

选择了5行。

SQL>


(未经测试)这样的东西可以完成这项工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

还有分析函数秩,可以用来排序。


与上面相同,但有修正。有效,但绝对不漂亮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   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

说实话,最好用上面的答案。