Mesh Expressions: Verknüpfte Tabellen effizient navigieren

kategorie
ABAP
Veröffentlicht
autor
Johannes

Mesh Expressions sind ein mächtiges Feature in ABAP, das seit Release 7.40 SP05 verfügbar ist. Sie ermöglichen die Navigation zwischen verknüpften internen Tabellen über definierte Assoziationen - ähnlich wie Fremdschlüssel-Beziehungen in relationalen Datenbanken.

Was ist ein Mesh?

Ein Mesh ist ein typisierter Verbund von internen Tabellen, die über Assoziationen miteinander verknüpft sind. Statt verschachtelte LOOPs zu schreiben, navigierst du deklarativ von einer Tabelle zur anderen.

KonzeptBeschreibung
TYPES MESHDefiniert den Mesh-Typ mit Tabellen und Assoziationen
AssociationVerknüpfung zwischen zwei Tabellen im Mesh
Mesh PathNavigationspfad über Assoziationen
\associationSyntax für Forward-Navigation
\_associationSyntax für Backward-Navigation

Anwendungsfälle für Mesh Expressions

  • Hierarchische Daten: Header-Item-Beziehungen (Auftrag → Positionen)
  • Master-Detail: Kunde → Aufträge → Positionen
  • Organisationsstrukturen: Abteilung → Teams → Mitarbeiter
  • Bill of Materials: Produkt → Komponenten → Unterkomponenten
  • Dokumentenfluss: Beleg → Folgebelege

TYPES MESH Definition

Die Grundstruktur eines Mesh beginnt mit der Typdefinition:

CLASS zcl_mesh_basic DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
" Strukturen für Header und Items
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.
" Tabellentypen
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.
" Mesh-Definition mit Assoziationen
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.
" Testdaten erstellen
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' )
).
" Mesh-Instanz erstellen
DATA(ls_orders) = VALUE ty_m_orders(
headers = lt_headers
items = lt_items
).
out->write( |Mesh erstellt mit { lines( ls_orders-headers ) } Headers| ).
out->write( |und { lines( ls_orders-items ) } Items| ).
ENDMETHOD.
ENDCLASS.

Erklärung der Mesh-Definition

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: Schlüsselwort für die Mesh-Typdefinition
  • ASSOCIATION name TO node: Definiert eine Assoziation zu einer anderen Node im Mesh
  • ON field1 = field2: Join-Bedingung (wie SQL-Joins)

Die wahre Stärke von Mesh Expressions liegt in der deklarativen Navigation:

CLASS zcl_mesh_navigation DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
" Strukturen
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.
" Tabellentypen
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 mit drei Ebenen
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.
" Testdaten aufbauen
DATA(lt_customers) = VALUE ty_t_customers(
( customer_id = 1 name = 'Müller GmbH' city = 'Berlin' )
( customer_id = 2 name = 'Schmidt AG' city = 'München' )
).
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 = 'Maus' quantity = 2 price = '25.00' )
( order_id = 101 item_no = 1 product = 'Tastatur' quantity = 1 price = '175.00' )
( order_id = 102 item_no = 1 product = 'Monitor' quantity = 2 price = '250.00' )
).
" Mesh erstellen
DATA(ls_sales) = VALUE ty_m_sales(
customers = lt_customers
orders = lt_orders
items = lt_items
).
" === Navigation: Customer -> Orders (1:n) ===
out->write( '=== Forward Navigation: Customer -> Orders ===' ).
LOOP AT ls_sales-customers INTO DATA(ls_customer).
out->write( |Kunde: { ls_customer-name }| ).
" Mesh-Pfad: Alle Aufträge des Kunden
LOOP AT ls_sales-customers\orders[ ls_customer ] INTO DATA(ls_order).
out->write( | Order { ls_order-order_id }: { ls_order-total } EUR| ).
" Verschachtelter Mesh-Pfad: Items des Auftrags
LOOP AT ls_sales-orders\items[ ls_order ] INTO DATA(ls_item).
out->write( | - { ls_item-product } x{ ls_item-quantity }| ).
ENDLOOP.
ENDLOOP.
ENDLOOP.
" === Backward Navigation: Item -> Order -> Customer ===
out->write( '' ).
out->write( '=== Backward Navigation: Item -> Order -> Customer ===' ).
LOOP AT ls_sales-items INTO DATA(ls_item2).
" Vom Item zurück zum Auftrag
DATA(ls_parent_order) = ls_sales-items\_order[ ls_item2 ].
" Vom Auftrag zurück zum Kunden
DATA(ls_parent_customer) = ls_sales-orders\_customer[ ls_parent_order ].
out->write( |{ ls_item2-product }: Order { ls_parent_order-order_id } von { ls_parent_customer-name }| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Mesh-Pfad Syntax

SyntaxBeschreibung
mesh-node\assoc[ source ]Forward-Navigation: Alle Ziele der Assoziation
mesh-node\_assoc[ source ]Backward-Navigation: Zurück zur Quelle
mesh-node\assoc1\assoc2[ source ]Verkettete Navigation über mehrere Assoziationen

Vorteile gegenüber verschachtelten LOOPs

Der größte Vorteil von Mesh Expressions zeigt sich beim Vergleich mit klassischem Code:

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.
" Testdaten
DATA(lt_departments) = VALUE ty_t_departments(
( dept_id = 1 dept_name = 'Entwicklung' )
( dept_id = 2 dept_name = 'Vertrieb' )
).
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-Einführung' budget = '80000.00' )
).
" Mesh erstellen
DATA(ls_org) = VALUE ty_m_organization(
departments = lt_departments
employees = lt_employees
projects = lt_projects
).
" ============================================
" KLASSISCHER ANSATZ: Verschachtelte LOOPs
" ============================================
out->write( '=== Klassischer Ansatz (verschachtelte LOOPs) ===' ).
LOOP AT lt_departments INTO DATA(ls_dept).
out->write( |Abteilung: { ls_dept-dept_name }| ).
LOOP AT lt_employees INTO DATA(ls_emp) WHERE dept_id = ls_dept-dept_id.
out->write( | Mitarbeiter: { ls_emp-emp_name }| ).
LOOP AT lt_projects INTO DATA(ls_proj) WHERE emp_id = ls_emp-emp_id.
out->write( | Projekt: { ls_proj-proj_name }| ).
ENDLOOP.
ENDLOOP.
ENDLOOP.
" ============================================
" MESH-ANSATZ: Deklarative Navigation
" ============================================
out->write( '' ).
out->write( '=== Mesh-Ansatz (deklarative Navigation) ===' ).
LOOP AT ls_org-departments INTO DATA(ls_dept2).
out->write( |Abteilung: { ls_dept2-dept_name }| ).
" Mesh-Navigation: Abteilung -> Mitarbeiter
LOOP AT ls_org-departments\employees[ ls_dept2 ] INTO DATA(ls_emp2).
out->write( | Mitarbeiter: { ls_emp2-emp_name }| ).
" Mesh-Navigation: Mitarbeiter -> Projekte
LOOP AT ls_org-employees\projects[ ls_emp2 ] INTO DATA(ls_proj2).
out->write( | Projekt: { ls_proj2-proj_name }| ).
ENDLOOP.
ENDLOOP.
ENDLOOP.
" ============================================
" VORTEIL: Aggregationen über Mesh-Pfade
" ============================================
out->write( '' ).
out->write( '=== Aggregation: Budget pro Abteilung ===' ).
LOOP AT ls_org-departments INTO DATA(ls_dept3).
" Alle Projekte einer Abteilung über Mitarbeiter
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 Projektbudget| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Vorteile im Überblick

AspektKlassisch (WHERE)Mesh
LesbarkeitVerschachtelte LOOPsDeklarative Pfade
WartbarkeitBedingungen verstreutBeziehungen zentral definiert
TypsicherheitRuntime-Fehler möglichCompiletime-Prüfung
PerformanceLinearer ScanNutzt Tabellenkeys
WiederverwendungCopy-PasteEinmal definieren

Praktisches Beispiel: Bill of Materials

Ein komplexeres Beispiel zeigt die Stärke von Mesh bei hierarchischen Strukturen:

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 für Stücklistenauflösung
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.
" Materialstamm
DATA(lt_materials) = VALUE ty_t_materials(
( material_id = 'BIKE_001' description = 'Mountainbike Komplett' unit = 'ST' )
( material_id = 'FRAME_01' description = 'Rahmen Aluminium' unit = 'ST' )
( material_id = 'WHEEL_01' description = 'Laufrad 26 Zoll' unit = 'ST' )
( material_id = 'TIRE_001' description = 'Reifen MTB' unit = 'ST' )
( material_id = 'SPOKE_01' description = 'Speiche Edelstahl' unit = 'ST' )
( material_id = 'BRAKE_01' description = 'Bremsanlage Disc' unit = 'ST' )
).
" Stückliste (Bill of Materials)
DATA(lt_bom) = VALUE ty_t_bom(
" Bike besteht aus:
( 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 )
" Laufrad besteht aus:
( parent_id = 'WHEEL_01' child_id = 'TIRE_001' quantity = 1 position = 10 )
( parent_id = 'WHEEL_01' child_id = 'SPOKE_01' quantity = 36 position = 20 )
).
" Lagerbestand
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 )
).
" Mesh erstellen
DATA(ls_bom) = VALUE ty_m_bom(
materials = lt_materials
bom = lt_bom
stock = lt_stock
).
" Stücklistenauflösung für Mountainbike
out->write( '=== Stücklistenauflösung: Mountainbike ===' ).
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
).
" Bedarfsermittlung für 10 Bikes
out->write( '' ).
out->write( '=== Bedarfsermittlung für 10 Mountainbikes ===' ).
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 }| ).
" Komponenten über Mesh-Navigation
LOOP AT is_mesh-materials\components[ is_material ] INTO DATA(ls_bom_item).
" Kind-Material über Mesh-Pfad
DATA(ls_child) = is_mesh-bom\child[ ls_bom_item ].
" Rekursiver Aufruf für Unterbaugruppen
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.
" Ausgangsmaterial
READ TABLE is_mesh-materials INTO DATA(ls_start) WITH KEY material_id = iv_material.
" Rekursive Bedarfsermittlung
collect_requirements(
is_mesh = is_mesh
is_material = ls_start
iv_quantity = iv_quantity
ct_requirements = lt_requirements
).
" Ergebnis ausgeben und mit Bestand vergleichen
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 über Mesh-Navigation
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 '✗ FEHLMENGE'
).
io_out->write( |{ ls_mat-description }: Bedarf { ls_req-quantity } / Bestand { lv_stock } { lv_status }| ).
ENDLOOP.
ENDMETHOD.
METHOD collect_requirements.
" Alle Komponenten sammeln
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.
" Bedarf sammeln
COLLECT VALUE ty_s_requirement(
material_id = ls_child-material_id
quantity = lv_child_qty
) INTO ct_requirements.
" Rekursiv für Unterbaugruppen
collect_requirements(
is_mesh = is_mesh
is_material = ls_child
iv_quantity = lv_child_qty
ct_requirements = ct_requirements
).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Einschränkungen und Hinweise

Mesh Expressions haben einige Einschränkungen, die du beachten solltest:

EinschränkungBeschreibung
Nur SORTED/HASHED TablesStandard Tables ohne Key funktionieren nicht optimal
Keine dynamischen AssoziationenBeziehungen müssen zur Compile-Zeit bekannt sein
SpeicherverbrauchMesh kopiert Tabellen (VALUE) oder referenziert sie (REF)
Keine DB-TabellenMesh arbeitet nur mit internen Tabellen

Performance-Tipps

" GUT: SORTED TABLE mit passendem Key
TYPES ty_t_items TYPE SORTED TABLE OF ty_s_item
WITH UNIQUE KEY order_id item_no.
" SCHLECHT: STANDARD TABLE ohne Key
" TYPES ty_t_items TYPE STANDARD TABLE OF ty_s_item
" WITH EMPTY KEY.
" GUT: Mesh-Komponenten als Referenz (spart Speicher)
DATA(ls_mesh) = VALUE ty_mesh(
headers = lt_headers " Kopie
items = REF #( lt_items ) " Referenz (bei großen Tabellen)
).

Fazit

Mesh Expressions bieten eine elegante Alternative zu verschachtelten LOOPs:

  • Deklarative Beziehungen: Einmal definieren, überall nutzen
  • Lesbarerer Code: Mesh-Pfade statt WHERE-Bedingungen
  • Typsicherheit: Compile-Time-Prüfung der Assoziationen
  • Performance: Nutzt Tabellenkeys automatisch
  • Wartbarkeit: Änderungen nur an einer Stelle

Besonders bei hierarchischen Datenstrukturen wie Bill of Materials, Organisationsstrukturen oder Header-Item-Beziehungen lohnt sich der Einsatz von Mesh Expressions.

Weiterführende Artikel