Pagination avec Oracle

97

Je ne suis pas aussi familier avec Oracle que je le souhaiterais. J'ai quelque 250 000 enregistrements et je souhaite en afficher 100 par page. Actuellement, j'ai une procédure stockée qui récupère tous les quart de million d'enregistrements dans un ensemble de données à l'aide d'un adaptateur de données, d'un ensemble de données et de la méthode dataadapter.Fill (ensemble de données) sur les résultats de la procédure stockée. Si j'ai "Numéro de page" et "Nombre d'enregistrements par page" comme valeurs entières que je peux passer en paramètres, quelle serait la meilleure façon de ne récupérer que cette section particulière. Dites, si je passe 10 comme numéro de page et 120 comme nombre de pages, à partir de l'instruction select, cela me donnerait le 1880e au 1200e, ou quelque chose comme ça, mes calculs dans ma tête pourraient être faux.

Je fais cela en .NET avec C #, je pensais que ce n'était pas important, si je peux le faire directement du côté SQL, alors je devrais être cool.

Mise à jour: J'ai pu utiliser la suggestion de Brian, et cela fonctionne très bien. J'aimerais travailler sur une optimisation, mais les pages arrivent dans 4 à 5 secondes plutôt qu'une minute, et mon contrôle de pagination a pu s'intégrer très bien avec mes nouveaux processus stockés.

Stephenbayer
la source

Réponses:

144

Quelque chose comme ça devrait fonctionner: Du blog de Frans Bouma

SELECT * FROM
(
    SELECT a.*, rownum r__
    FROM
    (
        SELECT * FROM ORDERS WHERE CustomerID LIKE 'A%'
        ORDER BY OrderDate DESC, ShippingDate DESC
    ) a
    WHERE rownum < ((pageNumber * pageSize) + 1 )
)
WHERE r__ >= (((pageNumber-1) * pageSize) + 1)
Brian Schmitt
la source
4
Oui, c'est une colonne «intégrée» qu'Oracle prend en charge, elle commence toujours à 1 et s'incrémente pour chaque ligne. Donc, dans cet extrait de code, si vous avez 1000 lignes, l'ordre de tri est appliqué, puis chaque ligne reçoit un rang. Les sélections externes utilisent ces numéros de ligne pour localiser la «page» que vous recherchez en fonction de la taille de votre page.
Brian Schmitt
9
C'est bien, mais horriblement lent sur les grandes sélections, il suffit de vérifier quel sera le temps de sélectionner 0 à 1000 et 500.000 à 501.000 ... J'utilisais ce type de structure de sélection maintenant je cherche une solution de contournement.
newhouse
3
@ n3whous3 vous pourriez essayer ceci - inf.unideb.hu/~gabora/pagination/results.html
jasonk
7
Je me suis demandé pourquoi deux WHEREne pouvaient pas être combinés avec AND, puis j'ai
Mengdi Gao
1
La pagination d'Oracle ruine ma journée.
Aetherus
134

Demandez à Tom la pagination et les fonctions analytiques très, très utiles.

Voici un extrait de cette page:

select * from (
    select /*+ first_rows(25) */
     object_id,object_name,
     row_number() over
    (order by object_id) rn
        from all_objects)
    where rn between :n and :m
        order by rn;
Chobicus
la source
7
C'est en fait une bien meilleure implémentation, même si elle est difficile à trouver sur ce post. Lorsque vous avez beaucoup de grandes pages, l'autre réponse doit également parcourir toutes les lignes des pages précédentes. Dans les requêtes complexes, cela signifie que les pages ultérieures sont moins performantes que les pages précédentes.
tallseth
@tallseth Vous avez raison. Il est difficile de le trouver sur cette page. Un extrait est ajouté.
Chobicus
C'est la bonne réponse si vous souhaitez modifier dynamiquement votre commande.
chakeda
74

Dans un souci d'exhaustivité, pour les personnes à la recherche d'une solution plus moderne, Oracle 12c propose de nouvelles fonctionnalités, notamment une meilleure pagination et une meilleure gestion.

Pagination

La pagination ressemble à ceci:

SELECT *
FROM user
ORDER BY first_name
OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY;

Top N records

Obtenir les meilleurs enregistrements ressemble à ceci:

SELECT *
FROM user
ORDER BY first_name
FETCH FIRST 5 ROWS ONLY

Notez que les deux exemples de requêtes ci-dessus ont des ORDER BYclauses. Les nouvelles commandes respectent ces derniers et sont exécutées sur les données triées.

Je n'ai pas trouvé de bonne page de référence Oracle pour FETCHou OFFSETmais cette page a un bon aperçu de ces nouvelles fonctionnalités.

Performance

Comme @wweicker le souligne dans les commentaires ci-dessous, les performances sont un problème avec la nouvelle syntaxe de 12c. Je n'avais pas de copie de 18c pour tester si Oracle l'a amélioré depuis.

Fait intéressant, mes résultats réels ont été renvoyés un peu plus rapidement la première fois que j'ai exécuté les requêtes sur ma table (113 millions + lignes) pour la nouvelle méthode:

  • Nouvelle méthode: 0,013 seconde.
  • Ancienne méthode: 0,107 seconde.

Cependant, comme @wweicker l'a mentionné, le plan d'explication semble bien pire pour la nouvelle méthode:

  • Coût de la nouvelle méthode: 300,110
  • Coût de l'ancienne méthode: 30

La nouvelle syntaxe a provoqué une analyse complète de l'index de ma colonne, ce qui représentait le coût total. Il y a de fortes chances que les choses empirent en se limitant aux données non indexées.

Jetons un coup d'œil lors de l'inclusion d'une seule colonne non indexée dans l'ensemble de données précédent:

  • Nouvelle méthode temps / coût: 189,55 secondes / 998,908
  • Durée / coût de l'ancienne méthode: 1,973 secondes / 256

Résumé: à utiliser avec précaution jusqu'à ce qu'Oracle améliore cette gestion. Si vous avez un index avec lequel travailler, vous pouvez peut-être vous en sortir avec la nouvelle méthode.

J'espère que j'aurai bientôt une copie de 18c avec laquelle jouer et que je pourrai mettre à jour

JoelC
la source
C'est une excellente réponse pour les utilisateurs de 12c
Lalji Gajera
1
La syntaxe est plus propre, mais les performances sont pires ( dba-present.com/index.php/databases/oracle/… )
wweicker
Bon à savoir, merci @wweicker. Espérons que la performance sera bientôt corrigée par Oracle; bien que, connaissant Oracle, cela pourrait être un espoir lointain!
JoelC
La syntaxe est nouvelle et elle est transformée en appels ROW_NUMBER / RANK normaux. Associé Comment limiter le nombre de lignes renvoyées par une requête Oracle après la commande?
Lukasz Szozda
@JoelC y a-t-il eu des changements dans votre opinion?
Ryan
11

Je veux juste résumer les réponses et les commentaires. Il existe plusieurs façons de réaliser une pagination.

Avant oracle 12c, il n'y avait pas de fonctionnalité OFFSET / FETCH, alors jetez un œil au livre blanc comme le suggère @jasonk. C'est l'article le plus complet que j'ai trouvé sur les différentes méthodes avec une explication détaillée des avantages et des inconvénients. Il faudrait beaucoup de temps pour les copier-coller ici, donc je ne le ferai pas.

Il y a aussi un bon article des créateurs de jooq expliquant certaines mises en garde courantes concernant la pagination d'Oracle et d'autres bases de données. article de blog de jooq

Bonne nouvelle, depuis oracle 12c nous avons une nouvelle fonctionnalité OFFSET / FETCH. Nouvelles fonctionnalités d'OracleMagazine 12c . Veuillez vous référer à "Requêtes et pagination Top-N"

Vous pouvez vérifier votre version d'Oracle en émettant la déclaration suivante

SELECT * FROM V$VERSION
Vadim Kirilchuk
la source
7

Essayez ce qui suit:

SELECT *
FROM
  (SELECT FIELDA,
    FIELDB,
    FIELDC,
    ROW_NUMBER() OVER (ORDER BY FIELDC) R
  FROM TABLE_NAME
  WHERE FIELDA = 10
  )
WHERE R >= 10
AND R   <= 15;

via [tecnicume]

Furetto
la source
0

Dans mon projet, j'ai utilisé Oracle 12c et java . Le code de pagination ressemble à ceci:

 public public List<Map<String, Object>> getAllProductOfferWithPagination(int pageNo, int pageElementSize, Long productOfferId, String productOfferName) {
    try {

        if(pageNo==1){
            //do nothing
        } else{
            pageNo=(pageNo-1)*pageElementSize+1;
        }
        System.out.println("algo pageNo: " + pageNo +"  pageElementSize: "+ pageElementSize+"  productOfferId: "+ productOfferId+"  productOfferName: "+ productOfferName);

        String sql = "SELECT * FROM ( SELECT * FROM product_offer po WHERE po.deleted=0 AND (po.product_offer_id=? OR po.product_offer_name LIKE ? )" +
             " ORDER BY po.PRODUCT_OFFER_ID asc) foo OFFSET ? ROWS FETCH NEXT ? ROWS ONLY ";

       return jdbcTemplate.queryForList(sql,new Object[] {productOfferId,"%"+productOfferName+"%",pageNo-1, pageElementSize});

    } catch (Exception e) {
        System.out.println(e);
        e.printStackTrace();
        return null;
    }
Ferdous Wahid
la source