1267 votes

Comment limiter le nombre de lignes renvoyées par une requête Oracle après avoir été ordonnée ?

Existe-t-il un moyen de faire un Oracle se comporte comme si elle contenait une requête MySQL limit clause ?

Dans MySQL, je peux faire cela :

select * 
from sometable
order by name
limit 20,10

pour obtenir les 21e à 30e rangs (sautez les 20 premiers, donnez les 10 suivants). Les rangs sont sélectionnés après le order by Il faut donc commencer par le 20ème nom par ordre alphabétique.

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

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

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

21 votes

Normalisé dans SQL:2008.

1 votes

Il semble que l'offset et le row_count soient inversés dans votre premier exemple MySQL. Cette requête sélectionnera les lignes 11 à 30, et non 21 à 30.

16 votes

Limite a été annoncé par Tom Kyte pour Oracle 12c...

928voto

Kosi2801 Points 9487

Vous pouvez utiliser une sous-requête pour cela, par exemple

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

Jetez également un coup d'œil à la rubrique Sur le ROWNUM et la limitation des résultats à Oracle/AskTom pour plus d'informations.

Mise à jour : Pour limiter le résultat avec des 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é de l'article spécifié AskTom-article)

Mise à jour 2 : À partir d'Oracle 12c (12.1), il existe une syntaxe permettant de limiter les lignes ou de commencer à des décalages.

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

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

5 votes

C'est sans aucun doute la façon de procéder, mais sachez (comme le dit l'article de Tom) que les performances des requêtes se dégradent à mesure que le nombre maximal de rotations augmente. Il s'agit d'une bonne solution pour les résultats de requêtes dont vous ne voulez voir que les premières pages, mais si vous utilisez cette méthode comme un mécanisme de code pour parcourir une table entière, vous feriez mieux de remanier votre code.

1 votes

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

2 votes

La "solution analytique avec une seule requête imbriquée" de Leigh Riffel est la bonne.

197voto

zeldi Points 2065

J'ai effectué quelques tests de performance 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 contient 10 millions d'enregistrements, le tri se fait sur une ligne de date non indexée :

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

La sélection des 10 premiers rangs a pris :

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

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

  • AskTom : 60 secondes
  • Analytique : 100 secondes

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

  • AskTom : 130 secondes
  • Analytique : 150 secondes

0 votes

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

5 votes

@MathieuLongtin BETWEEN est juste un raccourci pour >= AND <= ( stackoverflow.com/questions/4809083/between-clause-versus-and )

1 votes

Zeldi - Sur quelle version était-ce ? Oracle a apporté des améliorations aux performances analytiques dans les versions 11.1. et 11.2.

57voto

Leigh Riffel Points 2550

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() pourrait être remplacé par Row_Number() mais peut renvoyer plus d'enregistrements que ce que vous attendez s'il y a des valeurs en double pour le nom.

3 votes

J'adore les analyses. Vous pourriez vouloir clarifier la différence de comportement entre Rank() et Row_Number().

0 votes

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, RANK pourrait donner plus d'enregistrements que ce que vous attendez, donc vous devriez utiliser Row_Number.

0 votes

Si l'on mentionne rank() il convient également de noter dense_rank() qui peut être plus utile pour le contrôle de sortie, car il ne "saute" pas de chiffres, alors que rank() peut. En tout cas, pour cette question row_number() est le mieux adapté. Une autre remarque est que cette technique est applicable à toute base de données qui supporte les fonctions mentionnées.

30voto

beldaz Points 1432

Sur Oracle 12c (voir la clause de limitation de rangée dans Référence SQL ) :

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

61 votes

Et bien sûr, ils ont dû utiliser une syntaxe totalement différente de celle de tous les autres jusqu'à présent.

9 votes

Clairement, après s'être assis avec tous les autres vendeurs pour se mettre d'accord sur LIMIT dans SQL:2008, ils ont dû s'inspirer du livre de Microsoft et briser la norme.

1 votes

Il est intéressant de noter que j'ai entendu récemment que la norme la plus récente inclut cette syntaxe, donc peut-être qu'Oracle l'a introduite avant de la mettre en œuvre. On peut dire qu'elle est plus flexible que LIMIT ... OFFSET

6voto

Moins d'instructions SELECT. Aussi, moins de consommation de performance. 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 votes

De plus, c'est une réponse totalement incorrecte. La question portait sur la limitation APRÈS le tri. Donc rownum devrait être hors de la sous-requête.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X