ABAP REDUCE: Werte aggregieren und akkumulieren

kategorie
ABAP-Statements
Veröffentlicht
autor
Johannes

Der REDUCE-Ausdruck ermöglicht das Aggregieren und Akkumulieren von Werten über eine Iteration. Er ist vergleichbar mit fold oder reduce aus funktionalen Programmiersprachen und ersetzt viele LOOP-Konstrukte mit Akkumulator-Variablen.

Syntax

REDUCE <typ>(
INIT <akkumulator1> = <startwert1>
[ <akkumulator2> = <startwert2> ... ]
FOR <variable> IN <tabelle> [ WHERE ( <bedingung> ) ]
[ FOR <variable2> ... ]
NEXT <akkumulator1> = <ausdruck1>
[ <akkumulator2> = <ausdruck2> ... ]
)

Grundprinzip

  1. INIT: Initialisiert einen oder mehrere Akkumulatoren
  2. FOR: Iteriert über eine Tabelle (oder mehrere)
  3. NEXT: Aktualisiert die Akkumulatoren pro Iteration
  4. Das Ergebnis ist der Wert des ersten Akkumulators nach der letzten Iteration

Beispiele

1. Einfache Summe berechnen

DATA: lt_numbers TYPE TABLE OF i.
lt_numbers = VALUE #( ( 10 ) ( 20 ) ( 30 ) ( 40 ) ( 50 ) ).
" Klassisch mit LOOP
DATA: lv_sum TYPE i.
LOOP AT lt_numbers INTO DATA(lv_num).
lv_sum = lv_sum + lv_num.
ENDLOOP.
" Modern mit REDUCE
DATA(lv_sum2) = REDUCE i(
INIT sum = 0
FOR num IN lt_numbers
NEXT sum = sum + num
).
WRITE: / 'Summe:', lv_sum2. " 150

2. Summe aus Strukturtabelle

TYPES: BEGIN OF ty_order,
order_id TYPE i,
amount TYPE p DECIMALS 2,
END OF ty_order.
DATA: lt_orders TYPE TABLE OF ty_order.
lt_orders = VALUE #(
( order_id = 1 amount = '100.50' )
( order_id = 2 amount = '200.00' )
( order_id = 3 amount = '150.75' )
).
" Gesamtbetrag berechnen
DATA(lv_total) = REDUCE p DECIMALS 2(
INIT total = CONV p DECIMALS 2( 0 )
FOR ls_order IN lt_orders
NEXT total = total + ls_order-amount
).
WRITE: / 'Gesamtbetrag:', lv_total. " 451.25

3. Anzahl zählen mit WHERE

TYPES: BEGIN OF ty_product,
id TYPE i,
category TYPE string,
active TYPE abap_bool,
END OF ty_product.
DATA: lt_products TYPE TABLE OF ty_product.
lt_products = VALUE #(
( id = 1 category = 'A' active = abap_true )
( id = 2 category = 'B' active = abap_false )
( id = 3 category = 'A' active = abap_true )
( id = 4 category = 'A' active = abap_false )
).
" Anzahl aktiver Produkte der Kategorie A
DATA(lv_count) = REDUCE i(
INIT count = 0
FOR ls_prod IN lt_products
WHERE ( category = 'A' AND active = abap_true )
NEXT count = count + 1
).
WRITE: / 'Anzahl:', lv_count. " 2

4. Maximum finden

DATA: lt_values TYPE TABLE OF i.
lt_values = VALUE #( ( 42 ) ( 17 ) ( 99 ) ( 8 ) ( 73 ) ).
" Maximum finden
DATA(lv_max) = REDUCE i(
INIT max = 0
FOR val IN lt_values
NEXT max = nmax( val1 = max val2 = val )
).
WRITE: / 'Maximum:', lv_max. " 99

5. Minimum finden

" Minimum finden (mit erstem Wert als Startwert)
DATA(lv_min) = REDUCE i(
INIT min = lt_values[ 1 ]
FOR val IN lt_values
NEXT min = nmin( val1 = min val2 = val )
).
WRITE: / 'Minimum:', lv_min. " 8

6. Strings verketten

DATA: lt_names TYPE TABLE OF string.
lt_names = VALUE #( ( `Anna` ) ( `Bernd` ) ( `Clara` ) ).
" Namen mit Komma verketten
DATA(lv_concat) = REDUCE string(
INIT result = ``
FOR name IN lt_names
NEXT result = COND #(
WHEN result IS INITIAL THEN name
ELSE result && `, ` && name
)
).
WRITE: / lv_concat. " Anna, Bernd, Clara

7. Mehrere Akkumulatoren

TYPES: BEGIN OF ty_stats,
sum TYPE i,
count TYPE i,
END OF ty_stats.
DATA: lt_numbers TYPE TABLE OF i.
lt_numbers = VALUE #( ( 10 ) ( 20 ) ( 30 ) ( 40 ) ).
" Summe und Anzahl gleichzeitig berechnen
DATA(ls_stats) = REDUCE ty_stats(
INIT sum = 0
count = 0
FOR num IN lt_numbers
NEXT sum = sum + num
count = count + 1
).
DATA(lv_average) = ls_stats-sum / ls_stats-count.
WRITE: / 'Summe:', ls_stats-sum. " 100
WRITE: / 'Anzahl:', ls_stats-count. " 4
WRITE: / 'Durchschnitt:', lv_average. " 25

8. Tabelle aus Tabelle aufbauen

TYPES: ty_names TYPE TABLE OF string WITH EMPTY KEY.
DATA: lt_persons TYPE TABLE OF ty_person.
lt_persons = VALUE #(
( name = 'Max' age = 30 )
( name = 'Anna' age = 25 )
( name = 'Peter' age = 35 )
).
" Namen extrahieren in neue Tabelle
DATA(lt_names) = REDUCE ty_names(
INIT names = VALUE ty_names( )
FOR ls_person IN lt_persons
NEXT names = VALUE #( BASE names ( ls_person-name ) )
).
" Ergebnis: Max, Anna, Peter

9. Filtern und Aggregieren kombiniert

TYPES: BEGIN OF ty_sale,
region TYPE string,
amount TYPE p DECIMALS 2,
END OF ty_sale.
DATA: lt_sales TYPE TABLE OF ty_sale.
lt_sales = VALUE #(
( region = 'NORTH' amount = '1000.00' )
( region = 'SOUTH' amount = '2000.00' )
( region = 'NORTH' amount = '1500.00' )
( region = 'EAST' amount = '800.00' )
).
" Summe nur für Region NORTH
DATA(lv_north_total) = REDUCE p DECIMALS 2(
INIT total = CONV p DECIMALS 2( 0 )
FOR ls_sale IN lt_sales
WHERE ( region = 'NORTH' )
NEXT total = total + ls_sale-amount
).
WRITE: / 'NORTH Umsatz:', lv_north_total. " 2500.00

10. Verschachtelte Iteration (zwei FOR)

TYPES: BEGIN OF ty_category,
name TYPE string,
items TYPE TABLE OF i WITH EMPTY KEY,
END OF ty_category.
DATA: lt_categories TYPE TABLE OF ty_category.
lt_categories = VALUE #(
( name = 'A' items = VALUE #( ( 10 ) ( 20 ) ) )
( name = 'B' items = VALUE #( ( 30 ) ( 40 ) ( 50 ) ) )
).
" Summe aller Items über alle Kategorien
DATA(lv_total_all) = REDUCE i(
INIT total = 0
FOR ls_cat IN lt_categories
FOR lv_item IN ls_cat-items
NEXT total = total + lv_item
).
WRITE: / 'Gesamtsumme:', lv_total_all. " 150

11. FOR mit UNTIL/WHILE

" Iteration mit Bedingung (nicht über Tabelle)
DATA(lv_factorial) = REDUCE i(
INIT fact = 1
n = 1
UNTIL n > 5
NEXT fact = fact * n
n = n + 1
).
WRITE: / '5! =', lv_factorial. " 120
" Mit WHILE
DATA(lv_sum_while) = REDUCE i(
INIT sum = 0
i = 1
WHILE i <= 10
NEXT sum = sum + i
i = i + 1
).
WRITE: / 'Summe 1-10:', lv_sum_while. " 55

12. LET für lokale Hilfsvariablen

TYPES: BEGIN OF ty_item,
quantity TYPE i,
price TYPE p DECIMALS 2,
END OF ty_item.
DATA: lt_items TYPE TABLE OF ty_item.
lt_items = VALUE #(
( quantity = 5 price = '10.00' )
( quantity = 3 price = '25.00' )
( quantity = 10 price = '5.00' )
).
" Gesamtwert berechnen (Menge * Preis)
DATA(lv_total_value) = REDUCE p DECIMALS 2(
INIT total = CONV p DECIMALS 2( 0 )
FOR ls_item IN lt_items
LET line_total = ls_item-quantity * ls_item-price
IN
NEXT total = total + line_total
).
WRITE: / 'Gesamtwert:', lv_total_value. " 175.00

13. Gruppierung mit REDUCE

TYPES: BEGIN OF ty_group_result,
category TYPE string,
sum TYPE i,
END OF ty_group_result,
ty_group_results TYPE TABLE OF ty_group_result WITH EMPTY KEY.
DATA: lt_items TYPE TABLE OF ty_product.
lt_items = VALUE #(
( id = 1 category = 'A' active = abap_true )
( id = 2 category = 'B' active = abap_true )
( id = 3 category = 'A' active = abap_true )
( id = 4 category = 'A' active = abap_false )
( id = 5 category = 'B' active = abap_true )
).
" Anzahl pro Kategorie zählen
DATA(lt_by_category) = REDUCE ty_group_results(
INIT result = VALUE ty_group_results( )
FOR ls_item IN lt_items
FOR GROUPS <group> OF <item> IN lt_items
GROUP BY <item>-category
NEXT result = VALUE #(
BASE result
( category = <group> sum = REDUCE i(
INIT cnt = 0
FOR m IN GROUP <group>
NEXT cnt = cnt + 1
)
)
)
).

14. Boolean-Aggregation (ANY/ALL)

" Prüfen, ob MINDESTENS EIN Element die Bedingung erfüllt (ANY)
DATA(lv_any_active) = REDUCE abap_bool(
INIT any = abap_false
FOR ls_prod IN lt_products
NEXT any = xsdbool( any = abap_true OR ls_prod-active = abap_true )
).
" Prüfen, ob ALLE Elemente die Bedingung erfüllen (ALL)
DATA(lv_all_active) = REDUCE abap_bool(
INIT all = abap_true
FOR ls_prod IN lt_products
NEXT all = xsdbool( all = abap_true AND ls_prod-active = abap_true )
).
IF lv_any_active = abap_true.
WRITE: / 'Mindestens ein Produkt ist aktiv'.
ENDIF.
IF lv_all_active = abap_true.
WRITE: / 'Alle Produkte sind aktiv'.
ELSE.
WRITE: / 'Nicht alle Produkte sind aktiv'.
ENDIF.

15. REDUCE in Methodenaufrufen

METHODS: display_total
IMPORTING iv_total TYPE p.
" Direkt als Parameter
display_total(
iv_total = REDUCE p DECIMALS 2(
INIT sum = CONV p DECIMALS 2( 0 )
FOR ls_order IN lt_orders
NEXT sum = sum + ls_order-amount
)
).

Vergleich: REDUCE vs. LOOP

" === KLASSISCH MIT LOOP ===
DATA: lv_sum TYPE i,
lv_count TYPE i,
lv_max TYPE i.
LOOP AT lt_numbers INTO DATA(lv_num).
lv_sum = lv_sum + lv_num.
lv_count = lv_count + 1.
IF lv_num > lv_max.
lv_max = lv_num.
ENDIF.
ENDLOOP.
" === MODERN MIT REDUCE ===
DATA(ls_result) = REDUCE ty_result(
INIT sum = 0
count = 0
max = 0
FOR num IN lt_numbers
NEXT sum = sum + num
count = count + 1
max = nmax( val1 = max val2 = num )
).

Wichtige Hinweise / Best Practice

  • Der erste Akkumulator bestimmt den Rückgabetyp von REDUCE.
  • Für mehrere Ergebnisse: Verwenden Sie einen Struktur-Akkumulator oder mehrere Akkumulatoren.
  • FOR ... IN iteriert über Tabellen, FOR ... UNTIL/WHILE für bedingte Iteration.
  • WHERE filtert die Iteration – effizienter als IF im NEXT.
  • LET ermöglicht lokale Hilfsvariablen innerhalb der Iteration.
  • Verschachtelte FOR-Schleifen sind möglich für mehrdimensionale Daten.
  • BASE in VALUE #() erhält bestehende Tabelleneinträge beim Aufbau.
  • Kombinieren Sie mit VALUE, COND und FILTER.
  • REDUCE ist funktional – keine Seiteneffekte auf externe Variablen.
  • Bei komplexer Logik kann ein LOOP AT lesbarer sein.