注文後にOracleクエリによって返される行数を制限するにはどうすればよいですか?


1032

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

名前で並べられた10行のランダムなセットが返されますが、これは通常は必要ありません。また、オフセットを指定することもできません。


16
SQL:2008で標準化されました。
dalle

14
Oracle 12cのTom Kyteによって制限が発表されました...
12:11にwolφi

14
結果セットの次のページを取得していますか?
Mathieu Longtin 2013

3
@YaroslavShabalin特に、ページ検索では常にこのパターンが使用されます。あらゆる種類の検索機能を備えたほとんどすべてのアプリがそれを使用します。別の使用例は、長いリストまたはテーブルのクライアント側の一部のみをロードし、ユーザーに拡張するオプションを提供することです。
jpmc26 2014

3
@YaroslavShabalin基になるデータが変更されない限り、別の結果セットを取得することはできませんORDER BY。それが最初に注文するポイントです。基礎となるデータが変更され、それが原因で結果セットが変更された場合は、古い情報ではなく更新された結果をユーザーに表示しませんか?また、州の管理は、可能な限り回避すべき問題です。それは複雑さとバグの絶え間ない原因です。そのため、ファンクショナルは非常に人気があります。そして、いつメモリ内の結果セット全体を期限切れにすることを知っていますか?Webでは、ユーザーがいつ立ち去るかを知る方法はありません。
jpmc26 2014

回答:


621

オラクル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-articleからコピー)

Update 2:Oracle 12c(12.1)以降、行を制限したり、オフセットから開始したりできる構文があります。

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

その他の例については、この回答を参照してください。ヒントをくれたクルミアに感謝します。


5
これは間違いなくそれを行う方法ですが、(ask tomの記事にあるように)最大行数が増えるとクエリのパフォーマンスが低下することに注意してください。これは最初の数ページだけを表示したいクエリ結果の良い解決策ですが、これをコード全体のテーブル全体をページングするメカニズムとして使用している場合は、コードをリファクタリングする方が良いでしょう
Chris Gill

1
下位/上位バージョンを+1することで、実際に、上限のあるrownum句がクエリの速度を大幅に低下させる問題を回避できました。
ケルビン

1
リー・リフェルの「ネストされたクエリが1つだけの分析ソリューション」がその1つです。
Darren Hicks

7
AskTom記事には、SELECT / * + FIRST_ROWS(n)/ aを使用するオプティマイザヒントも含まれています、rownum rnum終了スラッシュの前にアスタリスクを付ける必要があります。SOはそれを洗い流しています。
David Mann

1
Oracle 11の場合、ROWNUMを使用した外部SELECTにより、(ORA-01446を使用した)UpdatableResultSetでdeleteRowを呼び出すことができなくなります-その12c R1の変更を楽しみにしています!
nsandersen

185

次のアプローチのパフォーマンステストをいくつか行いました。

アスクトム

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万のレコードがあり、ソートはインデックス付けされていない日時行で行われました。

  • Explainプランは3つの選択すべてに同じ値を示した(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秒

よくやった。> =や<=ではなく、betweenを使用した短い代替案を試しましたか?
Mathieu Longtin

4
@MathieuLongtin BETWEEN>= AND <=stackoverflow.com/questions/4809083/between-clause-versus-and)の省略形です
wweicker

1
zeldi-これはどのバージョンでしたか?Oracleは11.1で分析パフォーマンスを改善しました。および11.2
リーリフェル

@リー・リフェル10.2.0.5でした。ある日時間がかかるかもしれませんし、11iバージョンもチェックしています。
ゼルディ2013年

5
いくつかのクイックテストを実行し、12cでも同様の結果を得ました。新しいoffset構文は、分析アプローチと同じ計画とパフォーマンスを備えています。
Jon Heller、2014年

55

ネストされたクエリが1つだけの分析ソリューション:

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

Rank()の代わりに使用できRow_Number()ますが、名前の値が重複していると、予想よりも多くのレコードが返される可能性があります。


3
私は分析が大好きです。Rank()とRow_Number()の動作の違いを明確にしたい場合があります。
Dave Costa、

確かに、なぜ重複について考えなかったのかわかりません。したがって、この場合、nameに重複する値がある場合、RANKは予想よりも多くのレコードを提供する可能性があるため、Row_Numberを使用する必要があります。
リーリフェル

言及するrank()場合はdense_rank()、出力制御には数値が「スキップ」されないので、出力制御に便利な可能性があることに注意してくださいrank()。いずれにしても、この質問にrow_number()は最適です。もう1つの違いは、この手法が、言及された関数をサポートするすべてのデータベースに適用できることです。
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
明らかにLIMIT、SQL: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が標準の一部になることはありませんでした。
ベルダズ

14

Oracleでは、順序付けによるページ付けクエリは非常に扱いにくいものです。

Oracleは、データベースがテーブルまたは結合ビューのセットから行を選択する順序を示す数値を返すROWNUM疑似列を提供します。

ROWNUMは、多くの人を困らせる疑似列です。ROWNUM値が行に永続的に割り当てられるわけではありません(これはよくある誤解です)。ROWNUM値が実際に割り当てられると、混乱を招く可能性があります。ROWNUM値は、クエリのフィルター述語通過した後、クエリの集計または並べ替えの前に割り当てられます

さらに、ROWNUM値は、割り当てられた後にのみ増加します。

これが、フォローインクエリが行を返さない理由です。

 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

6

SQL標準

この記事で説明したように、SQL:2008標準には、SQL結果セットを制限するための次の構文が用意されています。

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

Oracle 11g以前のバージョン

バージョン12cより前は、上位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句でこれを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 BYとをrownum別々に行うことです。基本的に私はORDER BY節を持つサブクエリを作成しました
Patrick Gregorio

不正解なので反対票を投じてください。質問は、ソート後の制限についてでしたのでrownum、サブクエリの外にある必要があります。
Piotr Dobrogost

@PiotrDobrogost rownumは外部のみです。
サンディ

2

私はOracle 1z0-047試験の準備を開始し、12cに対して検証済みですそれを準備しているときに、「FETCH FIRST」と呼ばれる12cの拡張機能に出会いました。いくつかのオプションが利用可能です

- 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 2016年

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によってすでに投稿されていました。さらに、表示されているコードには構文エラーがあります。
Piotr Dobrogost

1

問合せによって返される各行について、ROWNUM疑似列は、Oracleが表または結合された行のセットから行を選択する順序を示す番号を返します。選択された最初の行のROWNUMは1、2番目の行は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;

ヴァル

    10
    10
     9
     9
     8

5行を選択しました。

SQL>


7
これがOracle 12cから適用され、どこかからコピー/貼り付けしたことを指定する必要があります-常にソースを引用してください。
マット

ソースはこの @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とORDER BYを保証するORDERは使用できません。
ベン

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
ORDER BYの前にWHERE句が評価されるため、これは誤りです。
ベン

3
興味深いことに、以下の私の悪い答えから盗まれました。
EvilTeach
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.