ABAP Mesh Expressions: Verknüpfte Tabellen navigieren ohne verschachtelte LOOPs

Kategorie
ABAP
Veröffentlicht
Autor
Johannes

Mesh Expressions ermöglichen die Navigation zwischen verknüpften internen Tabellen über definierte Assoziationen - ähnlich wie Joins in SQL, aber für interne Tabellen. Statt verschachtelte LOOPs mit WHERE-Bedingungen zu schreiben, definierst du die Beziehungen einmal zentral und navigierst dann deklarativ.

Was sind Mesh Expressions?

Ein Mesh ist ein typisierter Verbund von internen Tabellen mit definierten Assoziationen. Du definierst die Beziehungen zwischen den Tabellen (1:n, n:1) und kannst dann über Pfad-Ausdrücke navigieren.

KonzeptBeschreibung
TYPES MESHDefiniert den Mesh-Typ mit Tabellen und Assoziationen
ASSOCIATION name TO nodeVorwärts-Assoziation (z.B. Header → Items)
ON field = fieldJoin-Bedingung der Assoziation
mesh-node\assoc[ line ]Forward-Navigation: Alle zugehörigen Zeilen
mesh-node\_assoc[ line ]Backward-Navigation: Zurück zum Parent

Verfügbarkeit

Mesh Expressions sind seit ABAP 7.40 SP05 verfügbar und vollständig ABAP Cloud-kompatibel. In BTP ABAP Environment und S/4HANA Cloud können sie ohne Einschränkungen verwendet werden.

Praxisbeispiel: Bestellung mit Positionen und Konditionen

Ein typisches Szenario ist die Bestellabwicklung mit drei Ebenen:

  • Bestellkopf (Bestellnummer, Kunde, Datum)
  • Bestellpositionen (Artikel, Menge, Preis)
  • Konditionen (Rabatte, Zuschläge pro Position)

Klassischer Ansatz: Verschachtelte LOOPs

Ohne Mesh Expressions sieht der Code typischerweise so aus:

CLASS zcl_order_classic DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_s_order_header,
order_id TYPE sysuuid_x16,
customer TYPE string,
order_date TYPE d,
END OF ty_s_order_header.
TYPES: BEGIN OF ty_s_order_item,
order_id TYPE sysuuid_x16,
item_no TYPE i,
product TYPE string,
quantity TYPE i,
net_price TYPE p DECIMALS 2,
END OF ty_s_order_item.
TYPES: BEGIN OF ty_s_condition,
order_id TYPE sysuuid_x16,
item_no TYPE i,
condition_type TYPE c LENGTH 4,
amount TYPE p DECIMALS 2,
END OF ty_s_condition.
TYPES ty_t_headers TYPE STANDARD TABLE OF ty_s_order_header WITH EMPTY KEY.
TYPES ty_t_items TYPE STANDARD TABLE OF ty_s_order_item WITH EMPTY KEY.
TYPES ty_t_conditions TYPE STANDARD TABLE OF ty_s_condition WITH EMPTY KEY.
ENDCLASS.
CLASS zcl_order_classic IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Testdaten
DATA(lv_order1) = cl_system_uuid=>create_uuid_x16_static( ).
DATA(lt_headers) = VALUE ty_t_headers(
( order_id = lv_order1 customer = 'Müller GmbH' order_date = sy-datum )
).
DATA(lt_items) = VALUE ty_t_items(
( order_id = lv_order1 item_no = 10 product = 'Laptop' quantity = 2 net_price = '1200.00' )
( order_id = lv_order1 item_no = 20 product = 'Monitor' quantity = 3 net_price = '350.00' )
).
DATA(lt_conditions) = VALUE ty_t_conditions(
( order_id = lv_order1 item_no = 10 condition_type = 'RA01' amount = '-120.00' )
( order_id = lv_order1 item_no = 10 condition_type = 'ZU01' amount = '50.00' )
( order_id = lv_order1 item_no = 20 condition_type = 'RA01' amount = '-52.50' )
).
" Verschachtelte LOOPs - unübersichtlich
LOOP AT lt_headers INTO DATA(ls_header).
out->write( |Bestellung: { ls_header-customer }| ).
LOOP AT lt_items INTO DATA(ls_item)
WHERE order_id = ls_header-order_id.
out->write( | Position { ls_item-item_no }: { ls_item-product }| ).
LOOP AT lt_conditions INTO DATA(ls_cond)
WHERE order_id = ls_item-order_id
AND item_no = ls_item-item_no.
out->write( | Kondition { ls_cond-condition_type }: { ls_cond-amount }| ).
ENDLOOP.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Problem mit verschachtelten LOOPs

ProblemAuswirkung
Redundante WHERE-BedingungenFehleranfällig bei Änderungen
Schlechte PerformanceBei STANDARD TABLE linearer Scan
Unlesbarer CodeBei mehr Ebenen kaum noch wartbar
Keine WiederverwendungBedingungen immer wieder schreiben

Mesh-Ansatz: Deklarative Navigation

Mit Mesh Expressions definierst du die Beziehungen einmal und navigierst dann elegant:

CLASS zcl_order_mesh DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
" Strukturen
TYPES: BEGIN OF ty_s_order_header,
order_id TYPE sysuuid_x16,
customer TYPE string,
order_date TYPE d,
END OF ty_s_order_header.
TYPES: BEGIN OF ty_s_order_item,
order_id TYPE sysuuid_x16,
item_no TYPE i,
product TYPE string,
quantity TYPE i,
net_price TYPE p DECIMALS 2,
END OF ty_s_order_item.
TYPES: BEGIN OF ty_s_condition,
order_id TYPE sysuuid_x16,
item_no TYPE i,
condition_type TYPE c LENGTH 4,
amount TYPE p DECIMALS 2,
END OF ty_s_condition.
" SORTED Tables für optimale Performance
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.
TYPES ty_t_conditions TYPE SORTED TABLE OF ty_s_condition
WITH NON-UNIQUE KEY order_id item_no condition_type.
" Mesh-Definition mit allen Assoziationen
TYPES: BEGIN OF MESH ty_m_order,
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
ASSOCIATION conditions TO conditions ON order_id = order_id
AND item_no = item_no,
conditions TYPE ty_t_conditions
ASSOCIATION item TO items ON order_id = order_id
AND item_no = item_no,
END OF MESH ty_m_order.
ENDCLASS.
CLASS zcl_order_mesh IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Testdaten
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 = 'Müller GmbH' order_date = sy-datum )
( order_id = lv_order2 customer = 'Schmidt AG' order_date = sy-datum )
).
DATA(lt_items) = VALUE ty_t_items(
( order_id = lv_order1 item_no = 10 product = 'Laptop' quantity = 2 net_price = '1200.00' )
( order_id = lv_order1 item_no = 20 product = 'Monitor' quantity = 3 net_price = '350.00' )
( order_id = lv_order2 item_no = 10 product = 'Tastatur' quantity = 5 net_price = '75.00' )
).
DATA(lt_conditions) = VALUE ty_t_conditions(
( order_id = lv_order1 item_no = 10 condition_type = 'RA01' amount = '-120.00' )
( order_id = lv_order1 item_no = 10 condition_type = 'ZU01' amount = '50.00' )
( order_id = lv_order1 item_no = 20 condition_type = 'RA01' amount = '-52.50' )
( order_id = lv_order2 item_no = 10 condition_type = 'RA02' amount = '-15.00' )
).
" Mesh erstellen
DATA(ls_order_mesh) = VALUE ty_m_order(
headers = lt_headers
items = lt_items
conditions = lt_conditions
).
" Navigation mit Mesh-Pfaden
LOOP AT ls_order_mesh-headers INTO DATA(ls_header).
out->write( |Bestellung: { ls_header-customer }| ).
" Forward-Navigation: Header -> Items
LOOP AT ls_order_mesh-headers\items[ ls_header ] INTO DATA(ls_item).
out->write( | Position { ls_item-item_no }: { ls_item-product }| ).
" Forward-Navigation: Item -> Conditions
LOOP AT ls_order_mesh-items\conditions[ ls_item ] INTO DATA(ls_cond).
out->write( | Kondition { ls_cond-condition_type }: { ls_cond-amount }| ).
ENDLOOP.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Vergleich: Mesh vs. LOOP AT

Syntax-Vergleich

" KLASSISCH: WHERE-Bedingung bei jedem LOOP
LOOP AT lt_items INTO DATA(ls_item)
WHERE order_id = ls_header-order_id.
" ...
ENDLOOP.
" MESH: Beziehung einmal definiert, dann navigieren
LOOP AT ls_mesh-headers\items[ ls_header ] INTO DATA(ls_item).
" ...
ENDLOOP.

Feature-Vergleich

AspektKlassisch (LOOP + WHERE)Mesh Expressions
BeziehungsdefinitionVerstreut im CodeEinmal zentral
TypsicherheitRuntime-Fehler möglichCompile-Time-Prüfung
PerformanceAbhängig von TabellentypNutzt Keys automatisch
LesbarkeitWHERE-Bedingungen überallDeklarative Pfade
WartbarkeitÄnderungen an vielen StellenÄnderungen nur im Mesh
Backward-NavigationExplizite Suche nötigMit \_assoc eingebaut

Backward-Navigation: Vom Detail zum Header

Mit Mesh Expressions kannst du auch rückwärts navigieren:

CLASS zcl_mesh_backward DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_s_header,
order_id TYPE i,
customer TYPE string,
END OF ty_s_header.
TYPES: BEGIN OF ty_s_item,
order_id TYPE i,
item_no TYPE i,
product TYPE string,
END OF ty_s_item.
TYPES: BEGIN OF ty_s_condition,
order_id TYPE i,
item_no TYPE i,
condition_type TYPE string,
amount TYPE p DECIMALS 2,
END OF ty_s_condition.
TYPES ty_t_headers TYPE SORTED TABLE OF ty_s_header WITH UNIQUE KEY order_id.
TYPES ty_t_items TYPE SORTED TABLE OF ty_s_item WITH UNIQUE KEY order_id item_no.
TYPES ty_t_conditions TYPE SORTED TABLE OF ty_s_condition WITH NON-UNIQUE KEY order_id item_no.
TYPES: BEGIN OF MESH ty_m_order,
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
ASSOCIATION conditions TO conditions ON order_id = order_id
AND item_no = item_no,
conditions TYPE ty_t_conditions
ASSOCIATION item TO items ON order_id = order_id
AND item_no = item_no,
END OF MESH ty_m_order.
ENDCLASS.
CLASS zcl_mesh_backward IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_headers) = VALUE ty_t_headers(
( order_id = 1 customer = 'Kunde A' )
( order_id = 2 customer = 'Kunde B' )
).
DATA(lt_items) = VALUE ty_t_items(
( order_id = 1 item_no = 10 product = 'Laptop' )
( order_id = 2 item_no = 10 product = 'Server' )
).
DATA(lt_conditions) = VALUE ty_t_conditions(
( order_id = 1 item_no = 10 condition_type = 'Rabatt 10%' amount = '-100.00' )
( order_id = 2 item_no = 10 condition_type = 'Mengenrabatt' amount = '-500.00' )
).
DATA(ls_mesh) = VALUE ty_m_order(
headers = lt_headers
items = lt_items
conditions = lt_conditions
).
out->write( '=== Backward-Navigation: Kondition -> Item -> Header ===' ).
" Ausgehend von Konditionen zurück zum Kunden navigieren
LOOP AT ls_mesh-conditions INTO DATA(ls_cond).
" Backward: Kondition -> Item
DATA(ls_parent_item) = ls_mesh-conditions\_item[ ls_cond ].
" Backward: Item -> Header
DATA(ls_parent_header) = ls_mesh-items\_header[ ls_parent_item ].
out->write( |{ ls_cond-condition_type } ({ ls_cond-amount }) | &&
|-> { ls_parent_item-product } | &&
|-> { ls_parent_header-customer }| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Aggregationen mit REDUCE und Mesh

Mesh Expressions kombiniert mit REDUCE ermöglichen elegante Berechnungen:

CLASS zcl_mesh_aggregation DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_s_order,
order_id TYPE i,
customer TYPE string,
END OF ty_s_order.
TYPES: BEGIN OF ty_s_item,
order_id TYPE i,
item_no TYPE i,
quantity TYPE i,
net_price TYPE p DECIMALS 2,
END OF ty_s_item.
TYPES: BEGIN OF ty_s_condition,
order_id TYPE i,
item_no TYPE i,
condition_type TYPE c LENGTH 4,
amount TYPE p DECIMALS 2,
END OF ty_s_condition.
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.
TYPES ty_t_conditions TYPE SORTED TABLE OF ty_s_condition
WITH NON-UNIQUE KEY order_id item_no.
TYPES: BEGIN OF MESH ty_m_sales,
orders TYPE ty_t_orders
ASSOCIATION items TO items ON order_id = order_id,
items TYPE ty_t_items
ASSOCIATION order TO orders ON order_id = order_id
ASSOCIATION conditions TO conditions ON order_id = order_id
AND item_no = item_no,
conditions TYPE ty_t_conditions
ASSOCIATION item TO items ON order_id = order_id
AND item_no = item_no,
END OF MESH ty_m_sales.
ENDCLASS.
CLASS zcl_mesh_aggregation IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_orders) = VALUE ty_t_orders(
( order_id = 1 customer = 'Kunde A' )
( order_id = 2 customer = 'Kunde B' )
).
DATA(lt_items) = VALUE ty_t_items(
( order_id = 1 item_no = 10 quantity = 2 net_price = '1000.00' )
( order_id = 1 item_no = 20 quantity = 1 net_price = '500.00' )
( order_id = 2 item_no = 10 quantity = 5 net_price = '200.00' )
).
DATA(lt_conditions) = VALUE ty_t_conditions(
( order_id = 1 item_no = 10 condition_type = 'RA01' amount = '-200.00' )
( order_id = 1 item_no = 20 condition_type = 'RA01' amount = '-50.00' )
( order_id = 2 item_no = 10 condition_type = 'RA02' amount = '-100.00' )
).
DATA(ls_mesh) = VALUE ty_m_sales(
orders = lt_orders
items = lt_items
conditions = lt_conditions
).
out->write( '=== Bestellwert pro Kunde (inkl. Konditionen) ===' ).
LOOP AT ls_mesh-orders INTO DATA(ls_order).
" Summe aller Positionen
DATA(lv_net_total) = REDUCE p DECIMALS 2(
INIT sum = CONV p DECIMALS 2( 0 )
FOR <item> IN ls_mesh-orders\items[ ls_order ]
NEXT sum = sum + ( <item>-quantity * <item>-net_price )
).
" Summe aller Konditionen
DATA(lv_conditions_total) = REDUCE p DECIMALS 2(
INIT sum = CONV p DECIMALS 2( 0 )
FOR <item> IN ls_mesh-orders\items[ ls_order ]
FOR <cond> IN ls_mesh-items\conditions[ <item> ]
NEXT sum = sum + <cond>-amount
).
DATA(lv_grand_total) = lv_net_total + lv_conditions_total.
out->write( |{ ls_order-customer }:| ).
out->write( | Netto: { lv_net_total } EUR| ).
out->write( | Konditionen: { lv_conditions_total } EUR| ).
out->write( | Gesamt: { lv_grand_total } EUR| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Performance-Tipps

Tabellentypen wählen

" 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 - schlechte Performance
" TYPES ty_t_items TYPE STANDARD TABLE OF ty_s_item
" WITH EMPTY KEY.

Speicher sparen mit REF

Bei großen Tabellen kannst du Referenzen statt Kopien verwenden:

" Mesh mit Referenzen (spart Speicher)
DATA(ls_mesh) = VALUE ty_m_order(
headers = lt_headers " Kopie
items = REF #( lt_items ) " Referenz
conditions = REF #( lt_conditions )
).

ABAP Cloud Kompatibilität

AspektStatus
BTP ABAP Environment✅ Vollständig unterstützt
S/4HANA Cloud✅ Vollständig unterstützt
S/4HANA On-Premise✅ Ab 7.40 SP05
Released API Status✅ Kernsprachfeature

Mesh Expressions sind ein Kernsprachfeature von ABAP und nicht von Released API Restrictions betroffen. Sie können in allen ABAP Cloud-Umgebungen ohne Einschränkungen verwendet werden.

Best Practices für ABAP Cloud

" DO: SORTED/HASHED Tables mit sinnvollen Keys
TYPES ty_t_items TYPE SORTED TABLE OF ty_s_item
WITH UNIQUE KEY order_id item_no.
" DO: Mesh für komplexe Navigationen verwenden
LOOP AT ls_mesh-headers\items[ ls_header ] INTO DATA(ls_item).
" Klarer als WHERE-Bedingungen
ENDLOOP.
" DON'T: STANDARD TABLE ohne Key
" TYPES ty_t_items TYPE STANDARD TABLE OF ty_s_item
" WITH EMPTY KEY.
" DON'T: Mesh für einfache 1:1 Lookups
" Für einzelne Lookups ist READ TABLE effizienter

Wann Mesh verwenden?

AnwendungsfallEmpfehlung
Header-Item-Strukturen✅ Ideal für Mesh
Master-Detail-Beziehungen✅ Ideal für Mesh
Hierarchische Daten (BOM)✅ Ideal für Mesh
Einfacher 1:1 Lookup❌ READ TABLE ist ausreichend
DB-Tabellen direkt❌ Mesh nur für interne Tabellen
Dynamische Beziehungen❌ Assoziationen müssen statisch sein

Zusammenfassung

Mesh Expressions bieten eine elegante Alternative zu verschachtelten LOOPs:

  • Zentrale Definition: Beziehungen einmal im MESH-Typ definieren
  • Deklarative Navigation: Pfad-Syntax statt WHERE-Bedingungen
  • Typsicherheit: Compile-Time-Prüfung der Assoziationen
  • Bidirektionale Navigation: Forward (\) und Backward (\_)
  • Performance: Nutzt Tabellenkeys automatisch
  • ABAP Cloud-ready: Vollständig unterstützt in allen Cloud-Umgebungen

Besonders bei Bestellstrukturen mit Header, Positionen und Konditionen reduzieren Mesh Expressions die Komplexität erheblich und machen den Code wartbarer.

Weiterführende Artikel