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

StackOverflow https://stackoverflow.com/questions/470542

  •  19-08-2019
  •  | 
  •  

Domanda

C'è un modo per far comportare una Oracle query come se contenesse una MySQL limit clausola?

In MySQL, posso farlo:

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 vengono selezionate dopo order by, quindi inizia davvero il 20o nome in ordine alfabetico.

In rownum, l'unica cosa che la gente menziona è la <=> pseudo-colonna, ma viene valutata prima <=>, che significa questo:

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.

È stato utile?

Soluzione

A partire da Oracle 12c R1 (12.1), è a clausola di limitazione delle righe . Non utilizza la sintassi LIMIT familiare, ma può fare meglio il lavoro con più opzioni. Puoi trovare la sintassi completa qui .

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 da pagina collegata , nella speranza di prevenire la putrefazione dei link.

Configurazione

CREATE TABLE rownum_order_test (
  val  NUMBER
);

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

COMMIT;

Cosa c'è nella tabella?

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 N righe

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

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

5 rows selected.

Ottieni le prime x righe, se <=> th ha legami, ottieni tutte le righe collegate

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.

In alto <=>% di righe

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

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

4 rows selected.

Utilizzo di 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.

Puoi combinare l'offset con le 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.

Altri suggerimenti

Puoi usare una sottoquery per questo come

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

Dai un'occhiata anche all'argomento Attivo ROWNUM e risultati limitanti su Oracle / AskTom per ulteriori informazioni.

Aggiorna : Per limitare il risultato con entrambi i limiti inferiore e superiore, le cose diventano un po 'più gonfie 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;

(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 ulteriori esempi. Grazie a Krumia per il suggerimento.

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

analitica

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 l'analitica che segue da vicino)

La selezione delle prime 10 righe ha richiesto:

  • AskTom: 28-30 secondi
  • Analitico: 33-37 secondi
  • Breve alternativa: 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

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 con Row_Number() ma potrebbe restituire più record di quanto ti aspetti se ci sono valori duplicati per nome.

Su Oracle 12c (vedere la clausola di limitazione delle righe in Riferimento SQL ):

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

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 è 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 di 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 ROWNUM > 1 predicato, 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; 

Ulteriori informazioni sulle query di impaginazione nei miei articoli sul blog Vertabelo :

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;

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 persino spostarlo 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 incorporata e rinominando rownum come rnum. Puoi usare rnum nella query principale come criterio di filtro.

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

maggiore dei valori scopri

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

meno dei valori scopri

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

Ho iniziato a prepararmi per l'esame Oracle 1z0-047, convalidato contro 12c Mentre mi preparavo per questo mi sono imbattuto in un potenziamento 12c noto come 'FETCH FIRST' 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

SQL Standard

Come ho spiegato in questo articolo , l'SQL: Lo standard 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

Come estensione di risposta accettata Oracle utilizza internamente ROW_NUMBER/RANK funzioni. OFFSET FETCH la sintassi è uno zucchero sintattico.

Si può osservare usando la procedura DBMS_UTILITY.EXPAND_SQL_TEXT :

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;

Domanda:

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

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

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 

L'ho implementato nel oracle server 11.2.0.1.0

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 gt &;

(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

Esiste anche il rango di funzione analitica, che è possibile utilizzare per ordinare in base a.

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top