Mesh Expressions : Naviguer efficacement dans les tables liées

Catégorie
ABAP
Publié
Auteur
Johannes

Les Mesh Expressions sont une fonctionnalité puissante en ABAP, disponible depuis la Release 7.40 SP05. Elles permettent la navigation entre tables internes liées via des associations définies - similaire aux relations de clés étrangères dans les bases de données relationnelles.

Qu’est-ce qu’un Mesh ?

Un Mesh est un ensemble typé de tables internes qui sont liées entre elles par des associations. Au lieu d’écrire des LOOPs imbriqués, vous naviguez de manière déclarative d’une table à l’autre.

ConceptDescription
TYPES MESHDéfinit le type Mesh avec des tables et associations
AssociationLiaison entre deux tables dans le Mesh
Mesh PathChemin de navigation via associations
\associationSyntaxe pour navigation forward
\_associationSyntaxe pour navigation backward

Cas d’utilisation des Mesh Expressions

  • Données hiérarchiques : Relations En-tête-Postes (Commande → Postes)
  • Master-Detail : Client → Commandes → Postes
  • Structures organisationnelles : Département → Équipes → Employés
  • Bill of Materials : Produit → Composants → Sous-composants
  • Flux de documents : Document → Documents suivants

Définition TYPES MESH

La structure de base d’un Mesh commence par la définition du type :

CLASS zcl_mesh_basic DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
" Structures pour En-tête et Postes
TYPES: BEGIN OF ty_s_order_header,
order_id TYPE sysuuid_x16,
customer_id TYPE i,
order_date TYPE d,
status TYPE c LENGTH 1,
END OF ty_s_order_header.
TYPES: BEGIN OF ty_s_order_item,
order_id TYPE sysuuid_x16,
item_no TYPE i,
product_id TYPE c LENGTH 10,
quantity TYPE i,
price TYPE p DECIMALS 2,
END OF ty_s_order_item.
" Types de tables
TYPES ty_t_headers TYPE SORTED TABLE OF ty_s_order_header
WITH UNIQUE KEY order_id.
TYPES ty_t_items TYPE SORTED TABLE OF ty_s_order_item
WITH UNIQUE KEY order_id item_no.
" Définition Mesh avec associations
TYPES: BEGIN OF MESH ty_m_orders,
headers TYPE ty_t_headers
ASSOCIATION items TO items ON order_id = order_id,
items TYPE ty_t_items
ASSOCIATION header TO headers ON order_id = order_id,
END OF MESH ty_m_orders.
ENDCLASS.
CLASS zcl_mesh_basic IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Créer des données de test
DATA(lv_order1) = cl_system_uuid=>create_uuid_x16_static( ).
DATA(lv_order2) = cl_system_uuid=>create_uuid_x16_static( ).
DATA(lt_headers) = VALUE ty_t_headers(
( order_id = lv_order1 customer_id = 100 order_date = sy-datum status = 'N' )
( order_id = lv_order2 customer_id = 200 order_date = sy-datum status = 'C' )
).
DATA(lt_items) = VALUE ty_t_items(
( order_id = lv_order1 item_no = 10 product_id = 'PROD001' quantity = 2 price = '50.00' )
( order_id = lv_order1 item_no = 20 product_id = 'PROD002' quantity = 1 price = '100.00' )
( order_id = lv_order2 item_no = 10 product_id = 'PROD003' quantity = 5 price = '25.00' )
).
" Créer une instance Mesh
DATA(ls_orders) = VALUE ty_m_orders(
headers = lt_headers
items = lt_items
).
out->write( |Mesh créé avec { lines( ls_orders-headers ) } Headers| ).
out->write( |et { lines( ls_orders-items ) } Items| ).
ENDMETHOD.
ENDCLASS.

Explication de la définition Mesh

TYPES: BEGIN OF MESH ty_m_orders,
headers TYPE ty_t_headers
ASSOCIATION items TO items ON order_id = order_id,
items TYPE ty_t_items
ASSOCIATION header TO headers ON order_id = order_id,
END OF MESH ty_m_orders.
  • MESH : Mot-clé pour la définition du type Mesh
  • ASSOCIATION name TO node : Définit une association vers une autre node dans le Mesh
  • ON field1 = field2 : Condition de jointure (comme les jointures SQL)

La vraie force des Mesh Expressions réside dans la navigation déclarative :

CLASS zcl_mesh_navigation DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
" Structures
TYPES: BEGIN OF ty_s_customer,
customer_id TYPE i,
name TYPE string,
city TYPE string,
END OF ty_s_customer.
TYPES: BEGIN OF ty_s_order,
order_id TYPE i,
customer_id TYPE i,
order_date TYPE d,
total TYPE p DECIMALS 2,
END OF ty_s_order.
TYPES: BEGIN OF ty_s_item,
order_id TYPE i,
item_no TYPE i,
product TYPE string,
quantity TYPE i,
price TYPE p DECIMALS 2,
END OF ty_s_item.
" Types de tables
TYPES ty_t_customers TYPE SORTED TABLE OF ty_s_customer
WITH UNIQUE KEY customer_id.
TYPES ty_t_orders TYPE SORTED TABLE OF ty_s_order
WITH UNIQUE KEY order_id.
TYPES ty_t_items TYPE SORTED TABLE OF ty_s_item
WITH UNIQUE KEY order_id item_no.
" Mesh avec trois niveaux
TYPES: BEGIN OF MESH ty_m_sales,
customers TYPE ty_t_customers
ASSOCIATION orders TO orders ON customer_id = customer_id,
orders TYPE ty_t_orders
ASSOCIATION customer TO customers ON customer_id = customer_id
ASSOCIATION items TO items ON order_id = order_id,
items TYPE ty_t_items
ASSOCIATION order TO orders ON order_id = order_id,
END OF MESH ty_m_sales.
ENDCLASS.
CLASS zcl_mesh_navigation IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Construire des données de test
DATA(lt_customers) = VALUE ty_t_customers(
( customer_id = 1 name = 'Müller GmbH' city = 'Berlin' )
( customer_id = 2 name = 'Schmidt AG' city = 'Munich' )
).
DATA(lt_orders) = VALUE ty_t_orders(
( order_id = 100 customer_id = 1 order_date = '20260101' total = '250.00' )
( order_id = 101 customer_id = 1 order_date = '20260115' total = '175.00' )
( order_id = 102 customer_id = 2 order_date = '20260110' total = '500.00' )
).
DATA(lt_items) = VALUE ty_t_items(
( order_id = 100 item_no = 1 product = 'Laptop' quantity = 1 price = '200.00' )
( order_id = 100 item_no = 2 product = 'Souris' quantity = 2 price = '25.00' )
( order_id = 101 item_no = 1 product = 'Clavier' quantity = 1 price = '175.00' )
( order_id = 102 item_no = 1 product = 'Moniteur' quantity = 2 price = '250.00' )
).
" Créer le Mesh
DATA(ls_sales) = VALUE ty_m_sales(
customers = lt_customers
orders = lt_orders
items = lt_items
).
" === Navigation : Customer -> Orders (1:n) ===
out->write( '=== Navigation Forward : Customer -> Orders ===' ).
LOOP AT ls_sales-customers INTO DATA(ls_customer).
out->write( |Client : { ls_customer-name }| ).
" Chemin Mesh : Toutes les commandes du client
LOOP AT ls_sales-customers\orders[ ls_customer ] INTO DATA(ls_order).
out->write( | Commande { ls_order-order_id } : { ls_order-total } EUR| ).
" Chemin Mesh imbriqué : Postes de la commande
LOOP AT ls_sales-orders\items[ ls_order ] INTO DATA(ls_item).
out->write( | - { ls_item-product } x{ ls_item-quantity }| ).
ENDLOOP.
ENDLOOP.
ENDLOOP.
" === Navigation Backward : Item -> Order -> Customer ===
out->write( '' ).
out->write( '=== Navigation Backward : Item -> Order -> Customer ===' ).
LOOP AT ls_sales-items INTO DATA(ls_item2).
" Du poste vers la commande
DATA(ls_parent_order) = ls_sales-items\_order[ ls_item2 ].
" De la commande vers le client
DATA(ls_parent_customer) = ls_sales-orders\_customer[ ls_parent_order ].
out->write( |{ ls_item2-product } : Commande { ls_parent_order-order_id } de { ls_parent_customer-name }| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Syntaxe des chemins Mesh

SyntaxeDescription
mesh-node\assoc[ source ]Navigation forward : Toutes les cibles de l’association
mesh-node\_assoc[ source ]Navigation backward : Retour à la source
mesh-node\assoc1\assoc2[ source ]Navigation chaînée sur plusieurs associations

Avantages par rapport aux LOOPs imbriqués

Le plus grand avantage des Mesh Expressions apparaît dans la comparaison avec le code classique :

CLASS zcl_mesh_comparison DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_s_department,
dept_id TYPE i,
dept_name TYPE string,
END OF ty_s_department.
TYPES: BEGIN OF ty_s_employee,
emp_id TYPE i,
dept_id TYPE i,
emp_name TYPE string,
salary TYPE p DECIMALS 2,
END OF ty_s_employee.
TYPES: BEGIN OF ty_s_project,
proj_id TYPE i,
emp_id TYPE i,
proj_name TYPE string,
budget TYPE p DECIMALS 2,
END OF ty_s_project.
TYPES ty_t_departments TYPE SORTED TABLE OF ty_s_department WITH UNIQUE KEY dept_id.
TYPES ty_t_employees TYPE SORTED TABLE OF ty_s_employee WITH UNIQUE KEY emp_id.
TYPES ty_t_projects TYPE SORTED TABLE OF ty_s_project WITH UNIQUE KEY proj_id.
TYPES: BEGIN OF MESH ty_m_organization,
departments TYPE ty_t_departments
ASSOCIATION employees TO employees ON dept_id = dept_id,
employees TYPE ty_t_employees
ASSOCIATION department TO departments ON dept_id = dept_id
ASSOCIATION projects TO projects ON emp_id = emp_id,
projects TYPE ty_t_projects
ASSOCIATION employee TO employees ON emp_id = emp_id,
END OF MESH ty_m_organization.
ENDCLASS.
CLASS zcl_mesh_comparison IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Données de test
DATA(lt_departments) = VALUE ty_t_departments(
( dept_id = 1 dept_name = 'Développement' )
( dept_id = 2 dept_name = 'Ventes' )
).
DATA(lt_employees) = VALUE ty_t_employees(
( emp_id = 101 dept_id = 1 emp_name = 'Anna' salary = '5000.00' )
( emp_id = 102 dept_id = 1 emp_name = 'Ben' salary = '4500.00' )
( emp_id = 103 dept_id = 2 emp_name = 'Clara' salary = '5500.00' )
).
DATA(lt_projects) = VALUE ty_t_projects(
( proj_id = 1001 emp_id = 101 proj_name = 'App-Redesign' budget = '50000.00' )
( proj_id = 1002 emp_id = 101 proj_name = 'API-Migration' budget = '30000.00' )
( proj_id = 1003 emp_id = 102 proj_name = 'Testing' budget = '15000.00' )
( proj_id = 1004 emp_id = 103 proj_name = 'CRM-Introduction' budget = '80000.00' )
).
" Créer le Mesh
DATA(ls_org) = VALUE ty_m_organization(
departments = lt_departments
employees = lt_employees
projects = lt_projects
).
" ============================================
" APPROCHE CLASSIQUE : LOOPs imbriqués
" ============================================
out->write( '=== Approche classique (LOOPs imbriqués) ===' ).
LOOP AT lt_departments INTO DATA(ls_dept).
out->write( |Département : { ls_dept-dept_name }| ).
LOOP AT lt_employees INTO DATA(ls_emp) WHERE dept_id = ls_dept-dept_id.
out->write( | Employé : { ls_emp-emp_name }| ).
LOOP AT lt_projects INTO DATA(ls_proj) WHERE emp_id = ls_emp-emp_id.
out->write( | Projet : { ls_proj-proj_name }| ).
ENDLOOP.
ENDLOOP.
ENDLOOP.
" ============================================
" APPROCHE MESH : Navigation déclarative
" ============================================
out->write( '' ).
out->write( '=== Approche Mesh (navigation déclarative) ===' ).
LOOP AT ls_org-departments INTO DATA(ls_dept2).
out->write( |Département : { ls_dept2-dept_name }| ).
" Navigation Mesh : Département -> Employés
LOOP AT ls_org-departments\employees[ ls_dept2 ] INTO DATA(ls_emp2).
out->write( | Employé : { ls_emp2-emp_name }| ).
" Navigation Mesh : Employés -> Projets
LOOP AT ls_org-employees\projects[ ls_emp2 ] INTO DATA(ls_proj2).
out->write( | Projet : { ls_proj2-proj_name }| ).
ENDLOOP.
ENDLOOP.
ENDLOOP.
" ============================================
" AVANTAGE : Agrégations via chemins Mesh
" ============================================
out->write( '' ).
out->write( '=== Agrégation : Budget par département ===' ).
LOOP AT ls_org-departments INTO DATA(ls_dept3).
" Tous les projets d'un département via employés
DATA(lv_total_budget) = REDUCE p DECIMALS 2(
INIT sum = CONV p DECIMALS 2( 0 )
FOR <emp> IN ls_org-departments\employees[ ls_dept3 ]
FOR <proj> IN ls_org-employees\projects[ <emp> ]
NEXT sum = sum + <proj>-budget
).
out->write( |{ ls_dept3-dept_name } : { lv_total_budget } EUR Budget Projet| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Avantages en résumé

AspectClassique (WHERE)Mesh
LisibilitéLOOPs imbriquésChemins déclaratifs
MaintenabilitéConditions disperséesRelations définies centralement
Type-sécuritéErreurs runtime possiblesVérification compiletime
PerformanceScan linéaireUtilise les clés de table
RéutilisationCopy-PasteDéfinir une fois

Exemple pratique : Bill of Materials

Un exemple plus complexe montre la force du Mesh pour les structures hiérarchiques :

CLASS zcl_mesh_bom DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_s_material,
material_id TYPE c LENGTH 10,
description TYPE string,
unit TYPE c LENGTH 3,
END OF ty_s_material.
TYPES: BEGIN OF ty_s_bom_item,
parent_id TYPE c LENGTH 10,
child_id TYPE c LENGTH 10,
quantity TYPE p DECIMALS 3,
position TYPE i,
END OF ty_s_bom_item.
TYPES: BEGIN OF ty_s_stock,
material_id TYPE c LENGTH 10,
plant TYPE c LENGTH 4,
quantity TYPE p DECIMALS 3,
END OF ty_s_stock.
TYPES ty_t_materials TYPE SORTED TABLE OF ty_s_material WITH UNIQUE KEY material_id.
TYPES ty_t_bom TYPE SORTED TABLE OF ty_s_bom_item WITH NON-UNIQUE KEY parent_id position.
TYPES ty_t_stock TYPE SORTED TABLE OF ty_s_stock WITH UNIQUE KEY material_id plant.
" Mesh pour résolution de nomenclature
TYPES: BEGIN OF MESH ty_m_bom,
materials TYPE ty_t_materials
ASSOCIATION components TO bom ON material_id = parent_id
ASSOCIATION stock TO stock ON material_id = material_id,
bom TYPE ty_t_bom
ASSOCIATION parent TO materials ON parent_id = material_id
ASSOCIATION child TO materials ON child_id = material_id,
stock TYPE ty_t_stock
ASSOCIATION material TO materials ON material_id = material_id,
END OF MESH ty_m_bom.
ENDCLASS.
CLASS zcl_mesh_bom IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Données articles
DATA(lt_materials) = VALUE ty_t_materials(
( material_id = 'BIKE_001' description = 'VTT Complet' unit = 'PC' )
( material_id = 'FRAME_01' description = 'Cadre Aluminium' unit = 'PC' )
( material_id = 'WHEEL_01' description = 'Roue 26 pouces' unit = 'PC' )
( material_id = 'TIRE_001' description = 'Pneu VTT' unit = 'PC' )
( material_id = 'SPOKE_01' description = 'Rayon Inox' unit = 'PC' )
( material_id = 'BRAKE_01' description = 'Système freinage Disque' unit = 'PC' )
).
" Nomenclature (Bill of Materials)
DATA(lt_bom) = VALUE ty_t_bom(
" Vélo composé de :
( parent_id = 'BIKE_001' child_id = 'FRAME_01' quantity = 1 position = 10 )
( parent_id = 'BIKE_001' child_id = 'WHEEL_01' quantity = 2 position = 20 )
( parent_id = 'BIKE_001' child_id = 'BRAKE_01' quantity = 2 position = 30 )
" Roue composée de :
( parent_id = 'WHEEL_01' child_id = 'TIRE_001' quantity = 1 position = 10 )
( parent_id = 'WHEEL_01' child_id = 'SPOKE_01' quantity = 36 position = 20 )
).
" Stock
DATA(lt_stock) = VALUE ty_t_stock(
( material_id = 'FRAME_01' plant = '1000' quantity = 50 )
( material_id = 'WHEEL_01' plant = '1000' quantity = 100 )
( material_id = 'TIRE_001' plant = '1000' quantity = 200 )
( material_id = 'SPOKE_01' plant = '1000' quantity = 5000 )
( material_id = 'BRAKE_01' plant = '1000' quantity = 150 )
).
" Créer le Mesh
DATA(ls_bom) = VALUE ty_m_bom(
materials = lt_materials
bom = lt_bom
stock = lt_stock
).
" Résolution de nomenclature pour VTT
out->write( '=== Résolution nomenclature : VTT ===' ).
READ TABLE ls_bom-materials INTO DATA(ls_bike) WITH KEY material_id = 'BIKE_001'.
explode_bom(
is_mesh = ls_bom
is_material = ls_bike
iv_level = 0
iv_quantity = CONV #( 1 )
io_out = out
).
" Calcul de besoin pour 10 vélos
out->write( '' ).
out->write( '=== Calcul de besoin pour 10 VTT ===' ).
calculate_requirements(
is_mesh = ls_bom
iv_material = 'BIKE_001"
iv_quantity = 10
io_out = out
).
ENDMETHOD.
METHOD explode_bom.
DATA(lv_indent) = repeat( val = ` ` occ = iv_level ).
io_out->write( |{ lv_indent }{ is_material-description } ({ is_material-material_id }) x{ iv_quantity }| ).
" Composants via navigation Mesh
LOOP AT is_mesh-materials\components[ is_material ] INTO DATA(ls_bom_item).
" Article enfant via chemin Mesh
DATA(ls_child) = is_mesh-bom\child[ ls_bom_item ].
" Appel récursif pour sous-ensembles
explode_bom(
is_mesh = is_mesh
is_material = ls_child
iv_level = iv_level + 1
iv_quantity = iv_quantity * ls_bom_item-quantity
io_out = io_out
).
ENDLOOP.
ENDMETHOD.
METHOD calculate_requirements.
TYPES: BEGIN OF ty_s_requirement,
material_id TYPE c LENGTH 10,
quantity TYPE p DECIMALS 3,
END OF ty_s_requirement.
DATA lt_requirements TYPE SORTED TABLE OF ty_s_requirement
WITH UNIQUE KEY material_id.
" Article de départ
READ TABLE is_mesh-materials INTO DATA(ls_start) WITH KEY material_id = iv_material.
" Calcul récursif des besoins
collect_requirements(
is_mesh = is_mesh
is_material = ls_start
iv_quantity = iv_quantity
ct_requirements = lt_requirements
).
" Afficher le résultat et comparer avec stock
LOOP AT lt_requirements INTO DATA(ls_req).
READ TABLE is_mesh-materials INTO DATA(ls_mat) WITH KEY material_id = ls_req-material_id.
" Stock via navigation Mesh
DATA(lv_stock) = VALUE p DECIMALS 3( ).
LOOP AT is_mesh-materials\stock[ ls_mat ] INTO DATA(ls_stock).
lv_stock = lv_stock + ls_stock-quantity.
ENDLOOP.
DATA(lv_status) = COND string(
WHEN lv_stock >= ls_req-quantity THEN '✓ OK"
ELSE '✗ MANQUE"
).
io_out->write( |{ ls_mat-description } : Besoin { ls_req-quantity } / Stock { lv_stock } { lv_status }| ).
ENDLOOP.
ENDMETHOD.
METHOD collect_requirements.
" Collecter tous les composants
LOOP AT is_mesh-materials\components[ is_material ] INTO DATA(ls_bom).
DATA(ls_child) = is_mesh-bom\child[ ls_bom ].
DATA(lv_child_qty) = iv_quantity * ls_bom-quantity.
" Collecter le besoin
COLLECT VALUE ty_s_requirement(
material_id = ls_child-material_id
quantity = lv_child_qty
) INTO ct_requirements.
" Récursif pour sous-ensembles
collect_requirements(
is_mesh = is_mesh
is_material = ls_child
iv_quantity = lv_child_qty
ct_requirements = ct_requirements
).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Limitations et remarques

Les Mesh Expressions ont quelques limitations à prendre en compte :

LimitationDescription
Uniquement SORTED/HASHED TablesLes Standard Tables sans clé ne fonctionnent pas de manière optimale
Pas d’associations dynamiquesLes relations doivent être connues à la compilation
Consommation mémoireMesh copie les tables (VALUE) ou les référence (REF)
Pas de tables DBMesh ne fonctionne qu’avec des tables internes

Conseils de performance

" BON : SORTED TABLE avec clé appropriée
TYPES ty_t_items TYPE SORTED TABLE OF ty_s_item
WITH UNIQUE KEY order_id item_no.
" MAUVAIS : STANDARD TABLE sans clé
" TYPES ty_t_items TYPE STANDARD TABLE OF ty_s_item
" WITH EMPTY KEY.
" BON : Composants Mesh en référence (économise mémoire)
DATA(ls_mesh) = VALUE ty_mesh(
headers = lt_headers " Copie
items = REF #( lt_items ) " Référence (pour grandes tables)
).

Conclusion

Les Mesh Expressions offrent une alternative élégante aux LOOPs imbriqués :

  • Relations déclaratives : Définir une fois, utiliser partout
  • Code plus lisible : Chemins Mesh au lieu de conditions WHERE
  • Type-sécurité : Vérification Compile-Time des associations
  • Performance : Utilise automatiquement les clés de table
  • Maintenabilité : Modifications à un seul endroit

Particulièrement pour les structures de données hiérarchiques comme Bill of Materials, structures organisationnelles ou relations En-tête-Postes, l’utilisation des Mesh Expressions est avantageuse.

Articles complémentaires