Ce n’est pas parce que la perspective de me débarrasser bientôt de SAP se profile nettement à l’horizon que mes critiques vont se tarir.[1]. La semaine dernière j’ai découvert une nouvelle manière de l’ABAP de fusiller le travail de la base Oracle en-dessous de lui.

Un exemple concret en ventes : je recherche quelques articles (premier SELECT sur la table MARA) et je veux afficher la liste des commandes portant sur ces articles. Le résultat ressemble à ça :

Petite liste

Pour obtenir ceci, je peux :

  • soit faire un SELECT sur la table VBAP pour chaque article précédemment récupéré (méthode bovinement simple, fiable, très efficace si le but est de faire s’effondrer la base de données par des centaines ou des milliers de requêtes en rafale) ;
  • soit tenter de récupérer toutes les commandes concernées en bloc : la base recherchera d’un coup, optimisera son parcours de diverses manières (ne pas analyser 10 000 fois la même requête[2], récupérer des blocs contigus, choisir un index plutôt qu’un autre...).

La deuxième manière est un chouilla plus complexe (car on récupère en bloc un tableau des commandes voulues, qu’il faudra manipuler ensuite) mais monstrueusement plus efficace.
Si on utilise Oracle directement via la SQL ou le PL/SQL, on tente au maximum de tout regrouper en un seul ordre SQL, ou on utilise les ordres BULK.
Si on est contraint de passer par l’ABAP, on utilise FOR ALL ENTRIES[3].

Concrètement, cela donne ce qui suit. Noter qu’en ABAP un SELECT/ENDSELECT est une boucle sur les lignes ramenées.

*
* Report Z_FAE_TEST
*
*
* But : démonstration d utilisation basique de FOR ALL ENTRIES
* Recherche de quelques articles et affichage des commandes liées à
* ces articles. On aurait pu faire une simple jointure mais on passera
* par une table interne (un tableau) qui ontient ces commandes.
*

REPORT Z_FAE_TEST .

* vbap est la table des commandes, et la déclaration
* suivante permet de ne pas déclarer des variables temporaires
* pour la parcourir
TABLES: vbap.

* Structure de table simple avec juste un champ article
TYPES: BEGIN OF article,
matnr TYPE matnr, " type prédéfini : un article
END OF article.

* Table des articles
DATA: tab_articles TYPE TABLE OF article.

* Variable tampon car impossible de faire cette operation
* directement au sein du SQL ci-dessous.
DATA: date_min TYPE datum.

* Récupérer les articles créés dans les 200 derniers jours
date_min = sy-datum - 200.

SELECT matnr FROM mara
INTO TABLE tab_articles
WHERE ersda > date_min.
.


* Pour tous ces articles, afficher
* toutes les commandes existantes

SELECT * FROM vbap
* Prendre articles dans table interne précédente
FOR ALL ENTRIES IN tab_articles
* Jointure sur code article
WHERE vbap~matnr = tab_articles-matnr
.

* Affiche article/commande/poste
WRITE :/ vbap-matnr, vbap-vbeln, vbap-posnr .

ENDSELECT.

Quand on lit ceci, et qu’on connaît un peu Oracle, on se dit que l’ABAP sur ce point n’est pas trop mal foutu, et qu’il a même eu un temps d’avance sur l’Oracle « pur » d’avant l’implémentation de BULK COLLECT, qui forçait à tout fusionner en un seul ordre SQL si l’on voulait éviter les boucles.

On se dit que l’ABAP doit lui-même écrire l’ordre SQL qui va bien. Ou qu’il rédige une clause WHERE de centaines de lignes avec les flopées de valeurs possibles.

Mais non.

Le SQL que SAP demande à Oracle d’exécuter servilement se présente ainsi (transaction ST05 sous SAP) :

Trace SQL

Il s’agit bien d’un bête SELECT avec une condition sur... CINQ valeurs possibles. Ceci répété x fois avec des valeurs différentes.
SAP fait donc exécuter à Oracle la boucle même que l’on cherchait à éviter à la main. Le seul avantage est que cette recherche se fait par paquet de cinq lignes. C’est déjà mieux que rien mais on est loin du Saint Graal de l’instruction SQL unique.

Il existe un moyen de rentabiliser un peu plus chaque requête en passant à des bloc de vingt valeurs (rajouter le hint %_HINTS ORACLE '&max_blocking_factor 20&'), sans que j’en connaisse l’efficacité réelle.

Pourquoi faire simple (un bête curseur Oracle qui parcourt les lignes ramenées par paquets de quelques dizaines, avec un plan d’exécution mitonné pour l’intégrale du recordset à récupérer) quand on peut faire compliqué ?

Notes

[1] Sisi, un jour, je tenterai de faire un billet sur les gadgets sympa de SAP et de l’ABAP. Il y en a. Ajout de 2008 : C’est fait !

[2] Le plus terrible est de générer des requêtes en dynamique, réellement différentes, sans variables bind.

[3] Et c’est un des rares et précieux exemples de sucre syntaxique de ce langage fossile.