Bonjour à tous !
Le programme du jour : les boucles imbriquées en ABAP, bonnes pratiques et performance ! Alléchant, n’est-il pas ?
L’ABAP est un langage de programmation très orienté base de données, c’est-à-dire qu’il n’est pas rare, bien au contraire, de manipuler des quantités astronomiques de données. Et cela se fait la plupart du temps via ce que l’on appelle des tables internes, des variables qui vont contenir des extraits des tables de la base de données (ou d’autres données).
Et souvent, sur ces tables internes, il est nécessaire de boucler pour effectuer des traitements sur chaque enregistrement. Et il arrive parfois d’avoir une boucle imbriquée, et l’algorithme de base serait quelque chose comme :
BOUCLE sur chaque ligne de T1.
-- BOUCLE sur chaque ligne de T2.
-- -- traitement quelconque
-- Fin BOUCLE
Fin BOUCLE
On se rend compte aisément que pour chaque ligne de T1, l’ensemble de la table interne T2 sera parcouru. Et c’est rarement le comportement souhaité !
Illustrons un peu l’idée : nous avons une table interne T1 qui va contenir, disons, des données sur des clients (un client par ligne, et pas deux lignes pour un même client). Pour un très gros système, on peut aller jusqu’à 30 millions de clients ! Et ajoutons une table interne T2 qui contiendra l’ensemble des commandes des clients de T1 (en imaginant, évidemment, qu’un client passe plusieurs commandes). Et on souhaiterait, par exemple, sortir la somme des montants des commandes par client.
C’est un exemple bateau, mais ce sera sans doute plus clair que sans exemple du tout.
OK, alors, en reprenant l’algorithme montré plus haut, mais transformé en ABAP, on aurait :
LOOP AT T1 ASSIGNING <T1_line>.
-- sum = 0.
-- LOOP AT T2 ASSIGNING <T2_line>.
-- -- IF <T2_line>-customer = <T1_line>-customer.
-- -- -- sum = <T2_line>-amount + sum.
-- -- ENDIF.
-- ENDLOOP.
-- WRITE <T1_line>-customer, sum.
ENDLOOP.
=> Boucle sur la première table, initialisation de la somme pour le client, boucle sur la deuxième table, et ajout du montant à la somme. Enfin, affichage du client puis de la somme de ses factures. Rien de bien compliqué.
Le problème, c’est qu’en imaginant que T1 ait 10.000 clients et que T2 ait 50.000 commandes, cela signifie que les 50.000 lignes de T2 seraient parcourues 10.000 fois chacune ! C’est évidemment trop.
Et il est possible de faire autrement, et c’est là que nous entrons dans le vif du sujet !
Bon, alors, première étape : le type des tables internes doit être réfléchi ! Ici, il faut que la table interne des commandes (T2) soit triée par client.
Ensuite, l’idée est d’utiliser une boucle imbriquée indexée.
Enfin, stopper la boucle imbriquée lorsque l’on a traité toutes les lignes d’un même client.
Voici le code source que cela donnerait, que j’expliciterai ensuite :
LOOP AT T1 ASSIGNING <T1_line>.
-- sum = 0.
-- READ TABLE T2 WITH TABLE KEY customer = <T1_line>-customer TRANSPORTING NO FIELDS.
-- IF sy-subrc = 0.
-- -- index = sy-tabix.
-- ELSE.
-- -- CONTINUE.
-- ENDIF.
-- LOOP AT T2 ASSIGNING <T2_line> FROM index WHERE customer = <T1_line>-customer.
-- -- sum = <T2_line>-amount + sum.
-- ENDLOOP.
-- WRITE <T1_line>-customer, sum.
ENDLOOP.
Il n’y a pas tant de différences que cela par rapport à la première version, et pourtant, cela va tout changer !
Il s’agit donc toujours de boucler sur la table interne des clients (nous avions parlé de 10.000 entrées). La première différence réside dans l’instruction READ TABLE. Avant chaque boucle imbriquée, on va rechercher l’index de la première ligne de la table interne des commandes des clients (il est obligatoire que cette table interne soit triée par client !). En utilisant une table interne de type trié, l’instruction READ TABLE effectuera une recherche dichotomique, suffisamment rapide pour ne pas perdre en efficacité sur nos 10.000 READ TABLE qui seront effectués.
L’idée ensuite est soit de passer au tour de boucle suivant si le client n’a pas de commande, soit de démarrer la boucle imbriquée pour rechercher les commandes du client.
J’ai ajouté au LOOP AT T2 les additions FROM et WHERE qui vont d’une part permettre de définir l’index de la ligne à partir de laquelle démarrer la boucle (pour ne pas reprendre au début de boucle à chaque client), et d’autre part faire en sorte que lorsque toutes les commandes du client sont parcourues, la boucle s’interrompe d’elle-même.
Ainsi, pour chacun des 10.000 clients, la table interne des 50.000 commandes ne sera parcourue qu’un petit nombre de fois (pratiquement, seulement autant de fois que les clients ont de commandes, soit chaque ligne lue une seule fois).
Et c’est aussi simple que cela !
Pour résumer, s’il vous faut effectuer des boucles imbriquées, penser à indexer celles-ci afin de limiter au maximum le nombre de lignes parcourues. Cela diminuera d’autant les temps d’exécution. Et dans un environnement fortement volumétré, cela prend tout son sens.
Une réponse à “Les boucles imbriquées en ABAP”
Merci pour ce petit tuto ça m’aide grandement à optimiser un programme sur lequel je travaille en ce moment. Et bravo pour le site j’adore ce design sobre et fonctionnel. Bonne continuation. 😉