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

前端 未结 17 1468
夕颜
夕颜 2020-11-21 04:56

Is there a way to make an Oracle query behave like it contains a MySQL limit clause?

In MySQL, I can do this:

         


        
相关标签:
17条回答
  • 2020-11-21 05:02

    SQL Standard

    As I explained in this article, since version 12c Oracle supports the SQL:2008 Standard, which provides the following syntax to limit the SQL result set:

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

    Oracle 11g and older versions

    Prior to version 12c, to fetch the Top-N records, you had to use a derived table and the ROWNUM pseudocolumn:

    SELECT *
    FROM (
        SELECT
            title
        FROM
            post
        ORDER BY
            id DESC
    )
    WHERE ROWNUM <= 50
    
    0 讨论(0)
  • 2020-11-21 05:04

    Starting from Oracle 12c R1 (12.1), there is a row limiting clause. It does not use familiar LIMIT syntax, but it can do the job better with more options. You can find the full syntax here. (Also read more on how this works internally in Oracle in this answer).

    To answer the original question, here's the query:

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

    (For earlier Oracle versions, please refer to other answers in this question)


    Examples:

    Following examples were quoted from linked page, in the hope of preventing link rot.

    Setup

    CREATE TABLE rownum_order_test (
      val  NUMBER
    );
    
    INSERT ALL
      INTO rownum_order_test
    SELECT level
    FROM   dual
    CONNECT BY level <= 10;
    
    COMMIT;
    

    What's in the table?

    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.
    

    Get first N rows

    SELECT val
    FROM   rownum_order_test
    ORDER BY val DESC
    FETCH FIRST 5 ROWS ONLY;
    
           VAL
    ----------
            10
            10
             9
             9
             8
    
    5 rows selected.
    

    Get first N rows, if Nth row has ties, get all the tied rows

    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.
    

    Top x% of rows

    SELECT val
    FROM   rownum_order_test
    ORDER BY val
    FETCH FIRST 20 PERCENT ROWS ONLY;
    
           VAL
    ----------
             1
             1
             2
             2
    
    4 rows selected.
    

    Using an offset, very useful for pagination

    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.
    

    You can combine offset with percentages

    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.
    
    0 讨论(0)
  • 2020-11-21 05:04

    You can use a subquery for this like

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

    Have also a look at the topic On ROWNUM and limiting results at Oracle/AskTom for more information.

    Update: To limit the result with both lower and upper bounds things get a bit more bloated with

    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;
    

    (Copied from specified AskTom-article)

    Update 2: Starting with Oracle 12c (12.1) there is a syntax available to limit rows or start at offsets.

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

    See this answer for more examples. Thanks to Krumia for the hint.

    0 讨论(0)
  • 2020-11-21 05:05

    On Oracle 12c (see row limiting clause in SQL reference):

    SELECT * 
    FROM sometable
    ORDER BY name
    OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
    
    0 讨论(0)
  • 2020-11-21 05:05

    As an extension of accepted answer Oracle internally uses ROW_NUMBER/RANK functions. OFFSET FETCH syntax is a syntax sugar.

    It could be observed by using DBMS_UTILITY.EXPAND_SQL_TEXT procedure:

    Preparing sample:

    CREATE TABLE rownum_order_test (
      val  NUMBER
    );
    
    INSERT ALL
      INTO rownum_order_test
    SELECT level
    FROM   dual
    CONNECT BY level <= 10;
    COMMIT;
    

    Query:

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

    is regular:

    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 demo

    Fetching expanded SQL text:

    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 is expanded as 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
    

    and offset:

    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"
    
    0 讨论(0)
  • 2020-11-21 05:09

    Same as above with corrections. Works but definitely not pretty.

       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
    

    Honestly, better to use the above answers.

    0 讨论(0)
提交回复
热议问题