¿Cómo limito el número de filas devueltas por una consulta de Oracle después de ordenar?


1032

¿Hay alguna manera de hacer que una Oracleconsulta se comporte como si contuviera una MySQL limitcláusula?

En MySQL, puedo hacer esto:

select * 
from sometable
order by name
limit 20,10

para obtener las filas 21 a 30 (omita las primeras 20, dé las siguientes 10). Las filas se seleccionan después de order by, por lo que realmente comienza alfabéticamente en el vigésimo nombre.

En Oracle, lo único que la gente menciona es la rownumpseudo-columna, pero se evalúa antes order by , lo que significa esto:

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

devolverá un conjunto aleatorio de diez filas ordenadas por nombre, que generalmente no es lo que quiero. Tampoco permite especificar un desplazamiento.


16
Estandarizado en SQL: 2008.
dalle

14
Tom Kyte anunció el límite para Oracle 12c ...
wolφi

14
¿Buscando la página siguiente en un conjunto de resultados?
Mathieu Longtin

3
@YaroslavShabalin En particular, una búsqueda paginada usa este patrón todo el tiempo. Casi cualquier aplicación con cualquier tipo de función de búsqueda la va a usar. Otro caso de uso sería cargar solo una parte de una lista larga o una tabla del lado del cliente y darle al usuario la opción de expandirse.
jpmc26

3
@YaroslavShabalin No puede obtener un conjunto de resultados diferente a menos que los datos subyacentes cambien debido a ORDER BY. Ese es el punto de ordenar primero. Si los datos subyacentes cambian y su conjunto de resultados cambia debido a ello, ¿por qué no mostrar al usuario los resultados actualizados en lugar de la información desactualizada? Además, la gestión estatal es una plaga que debe evitarse tanto como sea posible. Es una fuente constante de complicaciones y errores; Es por eso que funcional se está volviendo tan popular. ¿Y cuándo sabrías caducar todo el conjunto de resultados en la memoria? En la web, no tienes forma de saber cuándo se va el usuario.
jpmc26

Respuestas:


621

A partir de Oracle 12c R1 (12.1), no es una fila cláusula limitante . No utiliza una LIMITsintaxis familiar , pero puede hacer el trabajo mejor con más opciones. Puede encontrar la sintaxis completa aquí . (También lea más sobre cómo funciona esto internamente en Oracle en esta respuesta ).

Para responder la pregunta original, aquí está la consulta:

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

(Para versiones anteriores de Oracle, consulte otras respuestas en esta pregunta)


Ejemplos:

Los siguientes ejemplos fueron citados de la página vinculada , con la esperanza de prevenir la pudrición de enlaces.

Preparar

CREATE TABLE rownum_order_test (
  val  NUMBER
);

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

COMMIT;

¿Qué hay en la mesa?

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.

Consigue las primeras Nfilas

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

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

5 rows selected.

Obtenga las primeras Nfilas, si Nla fila tiene empates, obtenga todas las filas atadas

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% Superior de filas

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

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

4 rows selected.

Usando un desplazamiento, muy útil para la paginación

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.

Puedes combinar offset con porcentajes

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
Solo para extender: la OFFSET FETCHsintaxis es un azúcar de sintaxis. Detalles
Lukasz Szozda

793

Puede usar una subconsulta para esto como

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

Consulte también el tema En ROWNUM y los resultados limitantes en Oracle / AskTom para obtener más información.

Actualización : para limitar el resultado con los límites inferior y superior, las cosas se hinchan un poco más con

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;

(Copiado del artículo AskTom especificado)

Actualización 2 : a partir de Oracle 12c (12.1) hay una sintaxis disponible para limitar las filas o comenzar en las compensaciones.

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

Vea esta respuesta para más ejemplos. Gracias a Krumia por la pista.


55
Esta es definitivamente la forma de hacerlo, pero tenga en cuenta (como dice el artículo de ask tom) que el rendimiento de la consulta se degrada a medida que aumenta su rownum máximo. Esta es una buena solución para los resultados de las consultas en las que solo desea ver las primeras páginas, pero si está utilizando esto como un mecanismo para que el código pase por una tabla completa, sería mejor refactorizar su código
Chris Gill

1
+1 su versión inferior / superior en realidad me ayudó a solucionar un problema en el que una simple cláusula de rownum con límite superior estaba ralentizando drásticamente mi consulta.
Kelvin

1
La "solución analítica de Leigh Riffel con una sola consulta anidada" es la indicada.
Darren Hicks

77
El artículo de AskTom también tiene una sugerencia de optimizador que usa SELECT / * + FIRST_ROWS (n) / a. , rownum rnum La barra oblicua debe estar precedida por un asterisco. SO lo está fregando.
David Mann

1
Tenga en cuenta que para Oracle 11 un SELECT externo con ROWNUM le impedirá llamar a deleteRow en un UpdatableResultSet (con ORA-01446). ¡Esperamos el cambio de 12c R1!
nsandersen

185

Hice algunas pruebas de rendimiento para los siguientes enfoques:

Asktom

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

Analítico

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

Alternativa corta

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

Resultados

La tabla tenía 10 millones de registros, la clasificación estaba en una fila de fecha y hora no indexada:

  • El plan de explicación mostró el mismo valor para las tres selecciones (323168)
  • Pero el ganador es AskTom (con seguimiento analítico muy cerca)

Seleccionar las primeras 10 filas tomó:

  • AskTom: 28-30 segundos
  • Analítico: 33-37 segundos
  • Alternativa corta: 110-140 segundos

Seleccionar filas entre 100,000 y 100,010:

  • AskTom: 60 segundos
  • Analítico: 100 segundos

Selección de filas entre 9,000,000 y 9,000,010:

  • AskTom: 130 segundos
  • Analítico: 150 segundos

Buen trabajo. ¿Intentó la alternativa corta con un medio en lugar de> = y <=?
Mathieu Longtin

44
@MathieuLongtin BETWEENes solo una abreviatura de >= AND <=( stackoverflow.com/questions/4809083/between-clause-versus-and )
wweicker

1
zeldi - ¿En qué versión estaba esto? Oracle ha realizado mejoras de rendimiento analítico en 11.1. y 11.2.
Leigh Riffel

@Leigh Riffel Era 10.2.0.5; algún día podría tomarme un tiempo y también revisar la versión 11i.
zeldi

55
Realicé algunas pruebas rápidas y obtuve resultados similares para 12c. La nueva offsetsintaxis tiene el mismo plan y rendimiento que el enfoque analítico.
Jon Heller

55

Una solución analítica con una sola consulta anidada:

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

Rank()podría sustituirse Row_Number()pero podría devolver más registros de los que espera si hay valores duplicados para el nombre.


3
Amo la analítica. Es posible que desee aclarar cuál sería la diferencia de comportamiento entre Rank () y Row_Number ().
Dave Costa el

De hecho, no estoy seguro de por qué no pensé en duplicados. Entonces, en este caso, si hay valores duplicados para el nombre, RANK podría dar más registros de los que espera, por lo tanto, debe usar Row_Number.
Leigh Riffel

Si lo menciona rank(), también vale la pena señalar dense_rank()qué puede ser más útil para el control de salida, ya que este último no "omite" los números, mientras que rank()puede. En cualquier caso para esta pregunta row_number()es el más adecuado. Otra no es que esta técnica es aplicable a cualquier base de datos que admita las funciones mencionadas.
Used_By_Already

28

En Oracle 12c (consulte la cláusula de limitación de filas en la referencia de SQL ):

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

53
Y, por supuesto, hasta ahora tenían que usar una sintaxis totalmente diferente a la de todos los demás
Mathieu Longtin,

99
Claramente, después de sentarse con todos los demás proveedores para acordar LIMITen SQL: 2008, tuvieron que sacar una hoja del libro de Microsoft y romper el estándar.
beldaz

1
Curiosamente, escuché recientemente que el estándar más reciente incluye esta sintaxis, por lo que tal vez Oracle lo introdujo primero antes de implementarlo. Podría decirse que es más flexible queLIMIT ... OFFSET
beldaz

3
@Derek: Sí, no seguir el estándar es lamentable. Pero la funcionalidad recientemente introducida en 12cR1 es más poderosa que solo LIMIT n, m(Ver mi respuesta). Por otra parte, Oracle debería haberse implementado LIMIT n, mcomo azúcar sintáctico, ya que es equivalente a OFFSET n ROWS FETCH NEXT m ROWS ONLY.
sampathsris

10
@Derek: En realidad, acabo de notar esta observación en el manual de PostgreSQL postgresql.org/docs/9.0/static/sql-select.html#AEN69535 "Las cláusulas LIMIT y OFFSET son sintaxis específica de PostgreSQL, también utilizada por MySQL. El SQL : 2008 estándar ha introducido las cláusulas OFFSET ... FETCH {FIRST | NEXT} ... para la misma funcionalidad ". Entonces LIMIT nunca fue parte del estándar.
beldaz

14

Las consultas de paginación con pedidos son realmente complicadas en Oracle.

Oracle proporciona una pseudocolumna ROWNUM que devuelve un número que indica el orden en que la base de datos selecciona la fila de una tabla o conjunto de vistas unidas.

ROWNUM es una pseudocolumna que mete en problemas a muchas personas. Un valor ROWNUM no se asigna permanentemente a una fila (este es un malentendido común). Puede ser confuso cuando se asigna un valor ROWNUM. Se asigna un valor ROWNUM a una fila después de pasar los predicados de filtro de la consulta pero antes de la agregación u ordenación de la consulta .

Además, un valor ROWNUM se incrementa solo después de ser asignado.

Es por eso que la consulta de seguimiento no devuelve filas:

 select * 
 from (select *
       from some_table
       order by some_column)
 where ROWNUM <= 4 and ROWNUM > 1; 

La primera fila del resultado de la consulta no pasa el predicado ROWNUM> 1, por lo que ROWNUM no se incrementa a 2. Por esta razón, ningún valor ROWNUM obtiene más de 1, por lo tanto, la consulta no devuelve filas.

La consulta definida correctamente debería verse así:

select *
from (select *, ROWNUM rnum
      from (select *
            from skijump_results
            order by points)
      where ROWNUM <= 4)
where rnum > 1; 

Obtenga más información sobre las consultas de paginación en mis artículos en el blog de Vertabelo :


2
La primera fila del resultado de la consulta no pasa ROWNUM> 1 predicado (...) - voto a favor para explicar esto.
Piotr Dobrogost

6

SQL estándar

Como expliqué en este artículo , el estándar SQL: 2008 proporciona la siguiente sintaxis para limitar el conjunto de resultados de SQL:

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

Oracle 11g y versiones anteriores

Antes de la versión 12c, para obtener los registros Top-N, tenía que usar una tabla derivada y la pseudocolumna ROWNUM:

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

5

Menos declaraciones SELECT. Además, consume menos rendimiento. Créditos a: 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
Además, es una respuesta totalmente incorrecta. La pregunta era acerca de limitar DESPUÉS de la clasificación. Entonces rownum debería estar fuera de subconsulta.
BitLord

5

Como una extensión de la respuesta aceptada, Oracle utiliza internamente ROW_NUMBER/RANKfunciones. OFFSET FETCHLa sintaxis es un azúcar de sintaxis.

Se pudo observar mediante el DBMS_UTILITY.EXPAND_SQL_TEXTprocedimiento:

Preparación de muestra:

CREATE TABLE rownum_order_test (
  val  NUMBER
);

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

Consulta:

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

es 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;

demostración de violín db <>

Obteniendo texto SQL expandido:

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 TIESse expande como 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

y compensación:

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

Si no está en Oracle 12C, puede usar la consulta TOP N como se muestra a continuación.

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

Incluso puede mover esto de la cláusula con la cláusula de la siguiente manera

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

Aquí en realidad estamos creando una vista en línea y renombrando rownum como rnum. Puede usar rnum en la consulta principal como criterio de filtro.


1
En mi caso, esto no devolvió las filas correctas. Lo que hice para solucionarlo es hacer el ORDER BYy el por rownumseparado. Básicamente, creé una subconsulta que tenía la ORDER BYcláusula .
Patrick Gregorio

Voto negativo ya que es una respuesta incorrecta. La pregunta era acerca de limitar después de la clasificación, por lo que rownumdebería estar fuera de una subconsulta.
Piotr Dobrogost

@PiotrDobrogost rownum está solo afuera.
sandi

2

Comencé a prepararme para el examen Oracle 1z0-047, validado contra 12c. Mientras me preparaba, me encontré con una mejora de 12c conocida como 'OBTENER PRIMERO' Le permite buscar filas / filas de límite según su conveniencia. Hay varias opciones disponibles con él.

- 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

Ejemplo:

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

3
stackoverflow.com/a/26051830/635608 : esto ya se ha proporcionado en otras respuestas. Abstenerse de publicar cosas que ya se publicaron hace meses.
Mat

1
oh claro, no examiné todas las respuestas, me encontré con las subconsultas al principio, lo tendré en cuenta.
arjun gaur

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

mayores que los valores averiguar

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

menos de los valores averiguar

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

El voto negativo como ROW_NUMBER()solución basada ya había sido publicado por Leigh Riffel. En adicción hay errores de sintaxis en el código que se muestra.
Piotr Dobrogost

1

Para cada fila devuelta por una consulta, la pseudocolumna ROWNUM devuelve un número que indica el orden en que Oracle selecciona la fila de una tabla o conjunto de filas unidas. La primera fila seleccionada tiene un ROWNUM de 1, la segunda tiene 2, y así sucesivamente.

  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 

He implementado esto en el oracleservidor11.2.0.1.0


voto negativo ya que la pregunta se refiere a limitar las filas ordenadas y ni siquiera tiene orden
Piotr Dobrogost

@PiotrDobrogost Tenga en cuenta que no es una tarea enorme, ordenar palabras clave es común para todos los rdbms, solo el límite tiene cambios.
Sumesh TG

-1

En el caso de SQL-Developer, recupera automáticamente solo las primeras 50 filas. ¡Y si nos desplazamos hacia abajo, obtiene otras 50 filas y así sucesivamente!

¡Por lo tanto, no necesitamos definir, en el caso de la herramienta sql-developer!


-3

En oráculo

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

VAL

    10
    10
     9
     9
     8

5 filas seleccionadas.

SQL>


77
Debe especificar que esto se aplica a partir de Oracle 12c, y que copia / pega desde algún lugar; siempre cite sus fuentes.
Mat

La fuente es esta @Mat. Y Rakesh, intente al menos adaptar la respuesta a la pregunta original. También proporcioné una respuesta citando la misma fuente, pero traté de ser exhaustiva y cité la fuente original.
sampathsris

-4

(no probado) algo como esto puede hacer el trabajo

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

También está el rango de función analítica, que puede usar para ordenar por.


2
Esto no devolverá una sola fila ya que ROWNUM es una columna en el conjunto de resultados, de modo que la última condición WHERE siempre será falsa. Además, no puede usar ROWNUM y un PEDIDO POR un PEDIDO de garantía.
Ben

2
Excelente. Dejemos esto aquí como una advertencia para los demás.
EvilTeach

-5

Igual que el anterior con correcciones. Funciona pero definitivamente no es bonito.

   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

Honestamente, es mejor usar las respuestas anteriores.


55
Esto es incorrecto ya que la cláusula WHERE se evalúa antes de ORDER BY.
Ben

3
Interesantemente robado de mi mala respuesta a continuación.
EvilTeach
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.