Come posso limitare il numero di righe restituite da una query Oracle dopo l'ordinazione?


1032

C'è un modo per far funzionare una Oraclequery come se contenesse una MySQL limitclausola?

In MySQL, posso fare questo:

select * 
from sometable
order by name
limit 20,10

per ottenere dalla 21a alla 30a fila (salta le prime 20, dai le successive 10). Le righe sono selezionate dopo il order by, quindi inizia davvero il 20 ° nome in ordine alfabetico.

In Oracle, l'unica cosa che la gente menziona è la rownumpseudo-colonna, ma viene valutata prima order by , il che significa che:

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

restituirà un set casuale di dieci righe ordinate per nome, che di solito non è quello che voglio. Inoltre, non consente di specificare un offset.


16
Standardizzato in SQL: 2008.
dalle

14
Il limite è stato annunciato da Tom Kyte per Oracle 12c ...
wolφi

14
Recupero della pagina successiva in un set di risultati?
Mathieu Longtin,

3
@YaroslavShabalin In particolare, una ricerca di paging utilizza questo schema tutto il tempo. Quasi ogni app con qualsiasi tipo di funzione di ricerca la utilizzerà. Un altro caso d'uso consisterebbe nel caricare solo una parte di un lungo elenco o lato client della tabella e dare all'utente la possibilità di espandersi.
jpmc26,

3
@YaroslavShabalin Non è possibile ottenere un set di risultati diverso a meno che i dati sottostanti non cambino a causa di ORDER BY. Questo è il punto principale per ordinare prima. Se i dati sottostanti cambiano e il set di risultati cambia a causa di ciò, perché non mostrare all'utente i risultati aggiornati anziché le informazioni obsolete? Inoltre, la gestione dello stato è una piaga da evitare il più possibile. È una fonte costante di complicazioni e bug; ecco perché il funzionale sta diventando così popolare. E quando vorresti far scadere l'intero set di risultati in memoria? Nel web, non hai modo di sapere quando l'utente lascia.
jpmc26,

Risposte:


621

Partendo da Oracle 12c R1 (12.1), non v'è una fila clausola limitativa . Non utilizza una LIMITsintassi familiare , ma può fare meglio il lavoro con più opzioni. Puoi trovare la sintassi completa qui . (Leggi anche di più su come funziona internamente in Oracle in questa risposta ).

Per rispondere alla domanda originale, ecco la query:

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

(Per le versioni precedenti di Oracle, fare riferimento ad altre risposte in questa domanda)


Esempi:

I seguenti esempi sono stati citati dalla pagina collegata , nella speranza di prevenire la putrefazione dei collegamenti.

Impostare

CREATE TABLE rownum_order_test (
  val  NUMBER
);

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

COMMIT;

Cosa c'è nel tavolo?

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.

Ottieni le prime Nfile

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

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

5 rows selected.

Ottenere prime Nrighe, se Nesima fila ha legami, ottenere tutte le righe legate

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% delle righe

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

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

4 rows selected.

Utilizzando un offset, molto utile per l'impaginazione

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.

È possibile combinare offset e percentuali

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 per estendere: la OFFSET FETCHsintassi è uno zucchero sintattico. Dettagli
Lukasz Szozda,

793

Puoi usare una subquery per questo tipo

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

Dai anche un'occhiata all'argomento Su ROWNUM e limita i risultati su Oracle / AskTom per maggiori informazioni.

Aggiornamento : per limitare il risultato con entrambi i limiti inferiore e superiore, le cose diventano un po 'più gonfie

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;

(Copiato dall'articolo AskTom specificato)

Aggiornamento 2 : a partire da Oracle 12c (12.1) è disponibile una sintassi per limitare le righe o iniziare dagli offset.

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

Vedi questa risposta per altri esempi. Grazie a Krumia per il suggerimento.


5
Questo è sicuramente il modo di farlo, ma fai attenzione (come dice l'articolo ask tom) le prestazioni della query diminuiscono all'aumentare del tuo rownum massimo. Questa è una buona soluzione per i risultati delle query in cui si desidera visualizzare solo le prime pagine, ma se si utilizza questo come meccanismo per passare da una tabella all'altra di un'intera tabella, sarebbe meglio effettuare il refactoring del codice
Chris Gill

1
+1 la tua versione inferiore / superiore in realtà mi ha aiutato a risolvere un problema in cui una semplice clausola di rownum con limite superiore stava drasticamente rallentando la mia query.
Kelvin,

1
La soluzione analitica di Leigh Riffel con una sola query nidificata è quella.
Darren Hicks,

7
L'articolo AskTom ha anche un suggerimento per l'ottimizzatore che utilizza SELECT / * + FIRST_ROWS (n) / a. , rownum rnum La barra di chiusura deve essere preceduta da un asterisco. SO lo sta cancellando.
David Mann,

1
Nota che per Oracle 11 un SELECT esterno con ROWNUM ti impedirà di chiamare deleteRow su un UpdatableResultSet (con ORA-01446) - in attesa di quel cambiamento di 12c R1!
nsandersen,

185

Ho eseguito alcuni test delle prestazioni per i seguenti approcci:

Asktom

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

Analitico

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

Breve alternativa

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

risultati

La tabella aveva 10 milioni di record, l'ordinamento era su una riga datetime non indicizzata:

  • Il piano di spiegazione ha mostrato lo stesso valore per tutte e tre le selezioni (323168)
  • Ma il vincitore è AskTom (con analitica che segue da vicino)

La selezione delle prime 10 righe ha richiesto:

  • AskTom: 28-30 secondi
  • Analitico: 33-37 secondi
  • Alternativa breve: 110-140 secondi

Selezione di righe tra 100.000 e 100.010:

  • AskTom: 60 secondi
  • Analitico: 100 secondi

Selezione di righe tra 9.000.000 e 9.000.010:

  • AskTom: 130 secondi
  • Analitico: 150 secondi

Bel lavoro. Hai provato la breve alternativa con una tra invece di> = e <=?
Mathieu Longtin,

4
@MathieuLongtin BETWEENè solo una scorciatoia per >= AND <=( stackoverflow.com/questions/4809083/between-clause-versus-and )
wweicker

1
zeldi - Su quale versione era presente? Oracle ha apportato miglioramenti alle prestazioni analitiche in 11.1. e 11.2.
Leigh Riffel,

@Leigh Riffel Era il 10.2.0.5; un giorno potrei prendere tempo e anche controllare la versione 11i.
zeldi,

5
Ho eseguito alcuni test rapidi e ho ottenuto risultati simili per 12c. La nuova offsetsintassi ha lo stesso piano e le stesse prestazioni dell'approccio analitico.
Jon Heller,

55

Una soluzione analitica con una sola query nidificata:

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

Rank()potrebbe essere sostituito Row_Number()ma potrebbe restituire più record di quanto ti aspetti se ci sono valori duplicati per nome.


3
Adoro l'analisi. Potresti voler chiarire quale sarebbe la differenza di comportamento tra Rank () e Row_Number ().
Dave Costa,

Anzi, non so perché non ho pensato ai duplicati. Quindi, in questo caso se ci sono valori duplicati per nome, RANK potrebbe fornire più record di quanto ti aspetti, quindi dovresti usare Row_Number.
Leigh Riffel,

Se menzionandolo rank()vale anche la pena notare dense_rank()quale potrebbe essere più utile per il controllo dell'output in quanto quest'ultimo non "salta" i numeri, mentre rank()può. In ogni caso, questa domanda row_number()è la più adatta. Un altro non è che questa tecnica è applicabile a qualsiasi db che supporti le funzioni menzionate.
Used_By_Al

28

Su Oracle 12c (vedere la clausola di limitazione delle righe nel riferimento SQL ):

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

53
E, naturalmente, hanno dovuto usare una sintassi totalmente diversa rispetto a tutti gli altri finora
Mathieu Longtin,

9
Chiaramente dopo essersi seduti con tutti gli altri fornitori per concordare LIMITin SQL: 2008, hanno dovuto prendere un foglio dal libro di Microsoft e infrangere lo standard.
Beldaz,

1
È interessante notare che recentemente ho sentito che lo standard più recente include questa sintassi, quindi forse Oracle l'ha inserita prima di implementarla. Probabilmente è più flessibile diLIMIT ... OFFSET
beldaz il

3
@Derek: Sì, non seguire lo standard è deplorevole. Ma la funzionalità appena introdotta in 12cR1 è più potente di un semplice LIMIT n, m(Vedi la mia risposta). Inoltre, Oracle avrebbe dovuto essere implementato LIMIT n, mcome zucchero sintattico, in quanto equivale a OFFSET n ROWS FETCH NEXT m ROWS ONLY.
sampathsris,

10
@Derek: In realtà, ho appena notato questa osservazione nel manuale PostgreSQL postgresql.org/docs/9.0/static/sql-select.html#AEN69535 "Le clausole LIMIT e OFFSET sono sintassi specifiche di PostgreSQL, usate anche da MySQL. : Lo standard 2008 ha introdotto le clausole OFFSET ... FETCH {FIRST | NEXT} ... per la stessa funzionalità ". Quindi LIMIT non faceva mai parte dello standard.
Beldaz,

14

Le query di impaginazione con l'ordinamento sono davvero complicate in Oracle.

Oracle fornisce una pseudocolonna ROWNUM che restituisce un numero che indica l'ordine in cui il database seleziona la riga da una tabella o da una serie di viste unite.

ROWNUM è una pseudocolonna che mette molte persone nei guai. Un valore ROWNUM non viene assegnato in modo permanente a una riga (questo è un malinteso comune). Potrebbe essere fonte di confusione quando un valore ROWNUM è effettivamente assegnato. Un valore ROWNUM viene assegnato a una riga dopo aver superato i predicati del filtro della query ma prima dell'aggregazione o dell'ordinamento della query .

Inoltre, un valore ROWNUM viene incrementato solo dopo che è stato assegnato.

Questo è il motivo per cui la query seguente non restituisce righe:

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

La prima riga del risultato della query non passa il predicato ROWNUM> 1, quindi ROWNUM non aumenta a 2. Per questo motivo, nessun valore ROWNUM diventa maggiore di 1, di conseguenza la query non restituisce righe.

La query definita correttamente dovrebbe apparire così:

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

Scopri di più sulle query di impaginazione nei miei articoli sul blog di Vertabelo :


2
La prima riga del risultato della query non passa ROWNUM> 1 predicato (...) - voto positivo per aver spiegato questo.
Piotr Dobrogost,

6

SQL Standard

Come ho spiegato in questo articolo , lo standard SQL: 2008 fornisce la sintassi seguente per limitare il set di risultati SQL:

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

Oracle 11g e versioni precedenti

Prima della versione 12c, per recuperare i record Top-N, era necessario utilizzare una tabella derivata e la pseudocolonna ROWNUM:

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

5

Meno istruzioni SELECT. Inoltre, meno consumo di prestazioni. Crediti 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
Inoltre, è una risposta totalmente errata. La domanda riguardava la limitazione DOPO lo smistamento. Quindi rownum dovrebbe essere fuori dalla subquery.
BitLord,

5

Come estensione della risposta accettata, Oracle utilizza internamente le ROW_NUMBER/RANKfunzioni. OFFSET FETCHla sintassi è uno zucchero sintattico.

Si potrebbe osservare usando la DBMS_UTILITY.EXPAND_SQL_TEXTprocedura:

Preparazione del campione:

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;

è regolare:

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 <> demo violino

Recupero del testo SQL espanso:

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è espanso come 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

e 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"

3

Se non si utilizza Oracle 12C, è possibile utilizzare la query TOP N come di seguito.

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

Puoi anche spostare questo dalla clausola con la clausola come segue

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

Qui in realtà stiamo creando una vista inline e rinominando rownum come rnum. È possibile utilizzare rnum nella query principale come criterio di filtro.


1
Nel mio caso questo non ha restituito le righe giuste. Quello che ho fatto per risolverlo è fare il ORDER BYe rownumseparatamente. Fondamentalmente ho creato una sottoquery che aveva la ORDER BYclausola .
Patrick Gregorio,

Downvote in quanto è una risposta errata. La domanda riguardava la limitazione dopo l'ordinamento, quindi rownumdovrebbe essere al di fuori di una sottoquery.
Piotr Dobrogost,

@PiotrDobrogost rownum è solo all'esterno.
sandi,

2

Ho iniziato a prepararmi per l'esame Oracle 1z0-047, convalidato contro 12c Mentre mi preparavo per questo mi sono imbattuto in un miglioramento 12c noto come 'FETCH FIRST' che ti consente di recuperare le righe / limitare le righe secondo la tua convenienza. Diverse opzioni sono disponibili con esso

- 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

Esempio:

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

3
stackoverflow.com/a/26051830/635608 - questo è già stato fornito in altre risposte. Si prega di astenersi dal pubblicare articoli già pubblicati mesi fa.
Mat

1
oh certo, non ho esaminato tutte le risposte, mi sono imbattuto in quelle secondarie all'inizio, lo terrò a mente.
Arjun Gaur,

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

maggiori quindi i valori scoprono

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

meno di valori scoprono

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

Il downvote come ROW_NUMBER()soluzione basata era già stato pubblicato da Leigh Riffel. Inoltre ci sono errori di sintassi nel codice mostrato.
Piotr Dobrogost,

1

Per ogni riga restituita da una query, la pseudocolonna ROWNUM restituisce un numero che indica l'ordine in cui Oracle seleziona la riga da una tabella o da un insieme di righe unite. La prima riga selezionata ha un ROWNUM di 1, la seconda ha 2 e così via.

  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 

Ho implementato questo nel oracleserver11.2.0.1.0


downvote come la domanda si pone sulla limitazione delle righe ordinate e non hai nemmeno l'ordine
Piotr Dobrogost

@PiotrDobrogost Capire che non è un compito enorme, ordinare parole chiave è comune per tutti i limiti di rdbms solo le modifiche.
Sumesh TG

-1

Nel caso di SQL-Developer, recupera automaticamente solo le prime 50 righe. E se scorriamo verso il basso, recupera altre 50 righe e così via!

Quindi non è necessario definire, in caso di strumento di sviluppo sql!


-3

In oracolo

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

VAL

    10
    10
     9
     9
     8

5 righe selezionate.

SQL>


7
È necessario specificare che ciò si applica a partire da Oracle 12c e che è stato copiato / incollato da qualche parte - si prega di citare sempre le proprie fonti.
Mat

La fonte è questa @Mat. E Rakesh, prova almeno ad adattare la risposta alla domanda originale. Ho anche fornito una risposta citando la stessa fonte, ma ho cercato di essere esaustivo e ho citato la fonte originale.
sampathsris,

-4

(non testato) qualcosa del genere potrebbe fare il lavoro

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

C'è anche il rango della funzione analitica, che puoi usare per ordinare.


2
Ciò non restituirà una singola riga poiché ROWNUM è una colonna nel gruppo di risultati, pertanto l'ultima condizione WHERE sarà sempre falsa. Inoltre non puoi utilizzare ROWNUM e un ORDINE DA UN ORDINE di garanzia.
Ben

2
Eccellente. Lasciamo questo qui come un avvertimento per gli altri.
EvilTeach

-5

Come sopra con correzioni. Funziona ma sicuramente non è carino.

   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

Onestamente, meglio usare le risposte di cui sopra.


5
Ciò non è corretto poiché la clausola WHERE viene valutata prima di ORDER BY.
Ben

3
Interessantemente rubato dalla mia cattiva risposta di seguito.
EvilTeach
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.