Comment limiter le nombre de lignes renvoyées par une requête Oracle après la commande?


1032

Existe-t-il un moyen de faire en sorte qu'une Oraclerequête se comporte comme si elle contenait une MySQL limitclause?

Dans MySQL, je peux le faire:

select * 
from sometable
order by name
limit 20,10

pour obtenir le 21e au 30e rangs (sauter les 20 premiers, donner les 10 suivants). Les lignes sont sélectionnées après le order by, donc ça commence vraiment par le 20ème nom par ordre alphabétique.

Dans Oracle, la seule chose que les gens mentionnent est la rownumpseudo-colonne, mais elle est évaluée avant order by , ce qui signifie ceci:

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

renverra un ensemble aléatoire de dix lignes ordonnées par nom, ce qui n'est généralement pas ce que je veux. Il ne permet pas non plus de spécifier un décalage.


16
Standardisé dans SQL: 2008.
dalle

14
Tom Kyte a annoncé la limite pour Oracle 12c ...
wolφi

14
Récupérer la page suivante dans un jeu de résultats?
Mathieu Longtin

3
@YaroslavShabalin En particulier, une recherche paginée utilise tout le temps ce modèle . Presque n'importe quelle application avec n'importe quel type de fonction de recherche va l'utiliser. Un autre cas d'utilisation serait de charger uniquement une partie d'une longue liste ou d'une table côté client et de donner à l'utilisateur la possibilité de se développer.
jpmc26

3
@YaroslavShabalin Vous ne pouvez pas obtenir un jeu de résultats différent à moins que les données sous-jacentes ne changent à cause du ORDER BY. C'est tout l'intérêt de commander en premier. Si les données sous-jacentes changent et que votre jeu de résultats change à cause de cela, alors pourquoi ne pas montrer à l'utilisateur les résultats mis à jour au lieu d'informations obsolètes? De plus, la gestion de l'État est un fléau à éviter autant que possible. C'est une source constante de complications et de bugs; c'est pourquoi la fonctionnalité devient si populaire. Et quand sauriez-vous expirer la totalité du jeu de résultats en mémoire? Sur le Web, vous n'avez aucun moyen de savoir quand l'utilisateur quitte.
jpmc26

Réponses:


621

À partir d'Oracle 12c R1 (12.1), il existe une clause de limitation de ligne . Il n'utilise pas de LIMITsyntaxe familière , mais il peut mieux faire le travail avec plus d'options. Vous pouvez trouver la syntaxe complète ici . (Lisez également la façon dont cela fonctionne en interne dans Oracle dans cette réponse ).

Pour répondre à la question d'origine, voici la requête:

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

(Pour les versions antérieures d'Oracle, veuillez vous référer aux autres réponses dans cette question)


Exemples:

Les exemples suivants ont été cités à partir de la page liée , dans l'espoir d'empêcher la pourriture des liens.

Installer

CREATE TABLE rownum_order_test (
  val  NUMBER
);

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

COMMIT;

Qu'y a-t-il dans le tableau?

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.

Obtenez les premières Nlignes

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

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

5 rows selected.

Obtenez les premières Nlignes, si Nla ligne a des liens, obtenez toutes les lignes liées

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% des lignes

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

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

4 rows selected.

Utiliser un offset, très utile pour la 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.

Vous pouvez combiner l'offset avec des pourcentages

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
Juste pour étendre: la OFFSET FETCHsyntaxe est un sucre de syntaxe. Détails
Lukasz Szozda

793

Vous pouvez utiliser une sous-requête pour cela comme

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

Consultez également la rubrique Sur ROWNUM et la limitation des résultats chez Oracle / AskTom pour plus d'informations.

Mise à jour : pour limiter le résultat avec les limites inférieures et supérieures, les choses deviennent un peu plus gonflées avec

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;

(Copié à partir de l'article AskTom spécifié)

Mise à jour 2 : à partir d'Oracle 12c (12.1), une syntaxe est disponible pour limiter les lignes ou démarrer à des décalages.

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

Voir cette réponse pour plus d'exemples. Merci à Krumia pour l'astuce.


5
C'est certainement la façon de le faire, mais sachez (comme le dit l'article Ask Tom) que les performances de la requête se dégradent à mesure que votre nombre maximal augmente. C'est une bonne solution pour les résultats de requête où vous ne voulez voir que les premières pages, mais si vous l'utilisez comme mécanisme pour que le code parcourt une table entière, vous feriez mieux de refactoriser votre code
Chris Gill

1
+1 votre version inférieure / supérieure m'a en fait aidé à contourner un problème où une simple clause de rownum à limite supérieure ralentissait considérablement ma requête.
Kelvin

1
La «solution analytique de Leigh Riffel avec une seule requête imbriquée» est celle-là.
Darren Hicks

7
L'article AskTom a également un indice d'optimiseur qui utilise SELECT / * + FIRST_ROWS (n) / a. , rownum rnum La barre oblique de fermeture doit être précédée d'un astérisque. SO le nettoie.
David Mann

1
Notez que pour Oracle 11, un SELECT externe avec ROWNUM vous empêchera d'appeler deleteRow sur un UpdatableResultSet (avec ORA-01446) - dans l'attente de ce changement 12c R1!
nsandersen

185

J'ai fait des tests de performances pour les approches suivantes:

Asktom

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

Analytique

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

Alternative courte

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

Résultats

La table comptait 10 millions d'enregistrements, le tri se faisait sur une ligne datetime non indexée:

  • Expliquer que le plan a montré la même valeur pour les trois sélections (323168)
  • Mais le gagnant est AskTom (suivi analytique de près)

La sélection des 10 premières lignes a pris:

  • AskTom: 28-30 secondes
  • Analytique: 33-37 secondes
  • Alternative courte: 110-140 secondes

Sélection de lignes entre 100 000 et 100 010:

  • AskTom: 60 secondes
  • Analytique: 100 secondes

Sélection de lignes entre 9 000 000 et 9 000 010:

  • AskTom: 130 secondes
  • Analytique: 150 secondes

Bon travail. Avez-vous essayé l'alternative courte avec un entre au lieu de> = et <=?
Mathieu Longtin

4
@MathieuLongtin BETWEENn'est qu'un raccourci pour >= AND <=( stackoverflow.com/questions/4809083/between-clause-versus-and )
wweicker

1
zeldi - Sur quelle version était-ce? Oracle a apporté des améliorations aux performances analytiques dans 11.1. et 11.2.
Leigh Riffel

@Leigh Riffel C'était 10.2.0.5; un jour, je pourrais prendre du temps et vérifier la version 11i.
zeldi

5
J'ai effectué des tests rapides et obtenu des résultats similaires pour 12c. La nouvelle offsetsyntaxe a le même plan et les mêmes performances que l'approche analytique.
Jon Heller

55

Une solution analytique avec une seule requête imbriquée:

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

Rank()peut être remplacé Row_Number()mais peut renvoyer plus d'enregistrements que vous ne le pensez s'il existe des valeurs en double pour le nom.


3
J'adore l'analyse. Vous voudrez peut-être clarifier la différence de comportement entre Rank () et Row_Number ().
Dave Costa

En effet, je ne sais pas pourquoi je n'ai pas pensé aux doublons. Donc, dans ce cas, s'il y a des valeurs en double pour le nom, alors RANK pourrait donner plus d'enregistrements que vous attendez, vous devez donc utiliser Row_Number.
Leigh Riffel le

Si vous le mentionnez, rank()il convient également de noter dense_rank()ce qui peut être plus utile pour le contrôle de la sortie car ce dernier ne "saute" pas les chiffres, alors qu'il le rank()peut. Dans tous les cas, cette question row_number()est la mieux adaptée. Un autre n'est pas cette technique est applicable à toute base de données qui prend en charge les fonctions mentionnées.
Used_By_Already

28

Sur Oracle 12c (voir la clause de limitation de ligne dans la référence SQL ):

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

53
Et bien sûr, ils devaient utiliser une syntaxe totalement différente de tout le monde jusqu'à présent
Mathieu Longtin

9
De toute évidence, après s'être assis avec tous les autres fournisseurs pour s'entendre sur LIMITSQL: 2008, ils ont ensuite dû retirer une feuille du livre de Microsoft et briser la norme.
beldaz

1
Fait intéressant, j'ai entendu récemment que la norme la plus récente inclut cette syntaxe, alors peut-être qu'Oracle l'a poussée avant de l'implémenter. On peut dire qu'il est plus flexible queLIMIT ... OFFSET
beldaz

3
@Derek: Oui, ne pas suivre la norme est regrettable. Mais la nouvelle fonctionnalité introduite dans 12cR1 est plus puissante que juste LIMIT n, m(voir ma réponse). Là encore, Oracle aurait dû implémenter LIMIT n, mcomme sucre syntaxique, car il est équivalent à OFFSET n ROWS FETCH NEXT m ROWS ONLY.
sampathsris

10
@Derek: En fait, je viens de remarquer cette remarque dans le manuel PostgreSQL postgresql.org/docs/9.0/static/sql-select.html#AEN69535 "Les clauses LIMIT et OFFSET sont une syntaxe spécifique à PostgreSQL, également utilisée par MySQL. Le SQL : La norme 2008 a introduit les clauses OFFSET ... FETCH {FIRST | NEXT} ... pour la même fonctionnalité ". LIMIT n'a donc jamais fait partie de la norme.
beldaz

14

Les requêtes de pagination avec ordre sont vraiment délicates dans Oracle.

Oracle fournit une pseudocolonne ROWNUM qui renvoie un nombre indiquant l'ordre dans lequel la base de données sélectionne la ligne dans une table ou un ensemble de vues jointes.

ROWNUM est une pseudocolonne qui cause des problèmes à de nombreuses personnes. Une valeur ROWNUM n'est pas affectée en permanence à une ligne (il s'agit d'un malentendu courant). Il peut être déroutant lorsqu'une valeur ROWNUM est réellement affectée. Une valeur ROWNUM est affectée à une ligne après avoir transmis les prédicats de filtre de la requête mais avant l'agrégation ou le tri de la requête .

De plus, une valeur ROWNUM n'est incrémentée qu'après avoir été affectée.

C'est pourquoi la requête de suivi ne renvoie aucune ligne:

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

La première ligne du résultat de la requête ne transmet pas le prédicat ROWNUM> 1, donc ROWNUM n'incrémente pas à 2. Pour cette raison, aucune valeur ROWNUM n'est supérieure à 1, par conséquent, la requête ne renvoie aucune ligne.

Une requête correctement définie doit ressembler à ceci:

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

En savoir plus sur les requêtes de pagination dans mes articles sur le blog Vertabelo :


2
La première ligne du résultat de la requête ne transmet pas le prédicat ROWNUM> 1 (…) - vote positif pour l'expliquer.
Piotr Dobrogost

6

SQL Standard

Comme je l'ai expliqué dans cet article , la norme SQL: 2008 fournit la syntaxe suivante pour limiter l'ensemble de résultats SQL:

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

Oracle 11g et versions antérieures

Avant la version 12c, pour récupérer les enregistrements Top-N, vous deviez utiliser une table dérivée et la pseudocolonne ROWNUM:

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

5

Moins d'instructions SELECT. En outre, moins consommatrice de performances. Crédits à: 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
De plus, c'est une réponse totalement incorrecte. La question était de limiter APRÈS le tri. Donc rownum devrait être hors de la sous-requête.
BitLord

5

En tant qu'extension de la réponse acceptée, Oracle utilise en interne des ROW_NUMBER/RANKfonctions. OFFSET FETCHla syntaxe est un sucre de syntaxe.

Il pourrait être observé en utilisant la DBMS_UTILITY.EXPAND_SQL_TEXTprocédure:

Préparation de l'échantillon:

CREATE TABLE rownum_order_test (
  val  NUMBER
);

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

Requete:

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

est régulier:

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;

démo db <> violon

Récupération du texte SQL développé:

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 est étendu comme 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

et 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

Si vous n'êtes pas sur Oracle 12C, vous pouvez utiliser la requête TOP N comme ci-dessous.

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

Vous pouvez même déplacer ceci de la clause avec la clause comme suit

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

Ici, en fait, nous créons une vue en ligne et renommons rownum en rnum. Vous pouvez utiliser rnum dans la requête principale comme critère de filtre.


1
Dans mon cas, cela n'a pas renvoyé les bonnes lignes. Ce que j'ai fait pour le réparer, c'est de faire le ORDER BYet le rownumséparément. Fondamentalement, j'ai créé une sous-requête contenant la ORDER BYclause .
Patrick Gregorio

Downvote car c'est une réponse incorrecte. La question portait sur la limitation après le tri et rownumdevrait donc être en dehors d'une sous-requête.
Piotr Dobrogost

@PiotrDobrogost rownum est uniquement à l'extérieur.
sandi

2

J'ai commencé à me préparer pour l'examen Oracle 1z0-047, validé contre 12c. En me préparant, je suis tombé sur une amélioration 12c connue sous le nom de `` FETCH FIRST ''. Elle vous permet de récupérer des lignes / limiter les lignes selon votre convenance. Plusieurs options sont disponibles avec elle

- 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

Exemple:

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

3
stackoverflow.com/a/26051830/635608 - cela a déjà été fourni dans d'autres réponses. Veuillez vous abstenir de publier des éléments déjà publiés il y a des mois.
Mat

1
Oh bien sûr, je n'ai pas répondu à toutes les réponses, je suis tombé sur les sous-requêtes très tôt, gardez cela à l'esprit.
arjun gaur

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

plus les valeurs découvrent

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

moins que les valeurs découvrent

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

Le downvote en tant que ROW_NUMBER()solution basée avait déjà été publié par Leigh Riffel. Dans la dépendance, il y a des erreurs de syntaxe dans le code affiché.
Piotr Dobrogost

1

Pour chaque ligne renvoyée par une requête, la pseudocolonne ROWNUM renvoie un nombre indiquant l'ordre dans lequel Oracle sélectionne la ligne dans une table ou un ensemble de lignes jointes. La première ligne sélectionnée a un ROWNUM de 1, la seconde en a 2, etc.

  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 

Je l'ai implémenté dans le oracleserveur11.2.0.1.0


downvote comme la question demande de limiter les lignes ordonnées et vous n'avez même pas d'ordre
Piotr Dobrogost

@PiotrDobrogost Comprenez que ce n'est pas une tâche énorme, les mots-clés de commande sont courants pour tous les rdbms, seule la limite a des changements.
Sumesh TG

-1

Dans le cas de SQL-Developer, il ne récupère automatiquement que les 50 premières lignes. Et si nous faisons défiler vers le bas, il récupère encore 50 lignes et ainsi de suite!

Par conséquent, nous n'avons pas besoin de définir, en cas d'outil sql-developer!


-3

Dans l'oracle

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

VAL

    10
    10
     9
     9
     8

5 lignes sélectionnées.

SQL>


7
Vous devez spécifier que cela s'applique à partir d'Oracle 12c et que vous copiez / collez cela quelque part - veuillez toujours citer vos sources.
mat

La source est ce @Mat. Et Rakesh, essayez au moins d'adapter la réponse à la question d'origine. J'ai également fourni une réponse en citant la même source, mais j'ai essayé d'être exhaustif et j'ai cité la source d'origine.
sampathsris

-4

(non testé) quelque chose comme ça peut faire le travail

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

Il y a aussi le classement de la fonction analytique, que vous pouvez utiliser pour classer.


2
Cela ne retournera pas une seule ligne car le ROWNUM est une colonne sur l'ensemble de résultats, de sorte que la dernière condition WHERE sera toujours fausse. De plus, vous ne pouvez pas utiliser ROWNUM et une COMMANDE PAR UNE COMMANDE DE GARANTIE.
Ben

2
Excellent. Laissons cela ici comme un avertissement aux autres.
EvilTeach

-5

Comme ci-dessus avec des corrections. Fonctionne mais certainement pas joli.

   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

Honnêtement, il vaut mieux utiliser les réponses ci-dessus.


5
Ceci est incorrect car la clause WHERE est évaluée avant ORDER BY.
Ben

3
Intéressant volé de ma mauvaise réponse ci-dessous.
EvilTeach du
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.