ABAP Deep Dive - FOR (Schleifen)
Schauen wir uns einmal die FOR Schleife etwas näher an. Wie funktioniert sie? Was muss ich beachten und was kann ich damit tun?
Inhaltsverzeichnis
Mit der Einführung neuer Statements in die ABAP Sprache, wurde auch ein Zusatz für den Loop entwickelt. Mit FOR kann man eine Schleife in einer Zeile abbilden, um so über Daten zu iterieren. In den folgenden Abschnitten schauen wir uns einmal die Ausprägungen dieser Form näher an.
Einleitung
Eines muss man Vorweg sagen, die FOR Schleife ist kein Ersatz für den LOOP. Sie funktioniert nur innerhalb der Statements REDUCE, NEW und VALUE, sogenannten Konstruktor Ausdrücken. In die FOR Schleife kann man auch nicht nach belieben Statements aufnehmen, wie es im LOOP der Fall ist.
Vorbereitung
Bevor wir mit den Beispielen starten, legen wir uns noch entsprechende Strukturen an, mit denen wir in den Beispielen arbeiten werden. Die Strukturen sind wie folgt definiert:
TYPES:
td_key TYPE c LENGTH 5,
BEGIN OF ts_simple_data,
tkey TYPE td_key,
text TYPE string,
number TYPE i,
date TYPE d,
END OF ts_simple_data,
tt_simple_data TYPE STANDARD TABLE OF ts_simple_data WITH EMPTY KEY,
BEGIN OF ts_easy_data,
tkey TYPE td_key,
text TYPE string,
number TYPE i,
END OF ts_easy_data,
tt_easy_data TYPE STANDARD TABLE OF ts_easy_data WITH EMPTY KEY,
tt_numbers TYPE STANDARD TABLE OF i WITH EMPTY KEY.
Dazu implementieren wir noch eine Methode, die uns einen Satz Testdaten zurück gibt:
METHOD generate_data.
rt_result = VALUE #(
( tkey = 'A' text = `Banana` number = 14 date = '20230101' )
( tkey = 'B' text = `Tomato` number = 12 date = '20230201' )
( tkey = 'C' text = `Apple` number = 23 date = '20231201' )
( tkey = 'E' text = `Strawberry` number = 31 date = '20230101' )
( tkey = 'F' text = `Salad` number = 20 date = '20230601' )
).
ENDMETHOD.
Zählvariable
Aus anderen Programmiersprachen wirst du sicherlich die Zählschleife kennen. Eine Schleife der man mitgibt, von wo nach wo gezählt werden soll und dann mit der entsprechenden Erhöhung der Zahl. Bauen wir dazu eine leere Tabelle mit Zahlen auf. Im VALUE Ausdruck implementieren wir nun die Schleife und beginnen mit FOR, dann der Variable und dem Startwert und bis wohin gezählt werden soll. Zum Abschluss kommt in die Klammern die zu erzeugende Struktur:
DATA(lt_numbers_until) = VALUE tt_numbers(
FOR i = 1 UNTIL i >= 10 ( i )
).
Neben dem Statement UNTIL gibt es auch WHILE, je nachdem, was man für seinen Einsatz bevorzugt. Ein Beispiel für diese Art von Schleife sieht ähnlich aus:
DATA(lt_numbers_while) = VALUE tt_numbers(
FOR i = 1 WHILE i <= 10 ( i )
).
Ist nichts angegeben, dann wird die Variable um +1 erhöht, hier gibt es aber auch noch die Möglichkeit die Zählweise anzupassen hinter THEN. Im folgenden Beispiel erhöhen wir den Zähler um +3:
DATA(lt_numbers_then) = VALUE tt_numbers(
FOR i = 1 THEN i + 3 WHILE i <= 10 ( i )
).
Die Ausgabe aus den drei Schleifen sieht wie folgt aus:
Datentyp
In den oberen Beispielen haben wir bisher immer mit Integer gezählt, es sind aber auch andere Datentypen wie Datum (D), Zeit (T) oder Numerisch (N) möglich. Als nächstes einmal ein Beispiel für eine Zählvariable mit Datum, dabei übernehmen wir nur jeden ungeraden Tag in unsere neue Tabelle:
DATA(lt_date_variable) = VALUE tt_simple_data(
FOR i = CONV d( '20230101' ) THEN i + 2 WHILE i <= '20230201' ( date = i )
).
Um den richtigen Datentypen zu erzeugen, konvertieren wir den Wert in die Variable, die Berechnung kommt mit dem Datumsfeld im Anschluss klar. Das Ergebnis der Tabelle sieht dann wie folgt aus:
FOREACH
In anderen Programmiersprachen wird die Schleife als FOREACH bezeichnet. Bei dieser Schleife geht es darum alle Datensätze einer Tabelle zu verarbeiten. Dies könnte man mit einem COUNT und einer Zählschleife machen, doch wieso kompliziert machen, wenn es auch einfach geht. Als erstes einmal benötigen wir eine Basistabelle:
DATA(lt_base) = generate_data( ).
Im nächsten Schritt bauen wir zwei Schleifen auf, eine Schleife die eine einfache Transformation mit CORRESPONDING macht und eine zweite Schleife, die die Felder übernimmt, aber einige Felder anders behandelt:
DATA(lt_corresponding) = VALUE tt_easy_data(
FOR ls_base IN lt_base ( CORRESPONDING #( ls_base ) )
).
DATA(lt_simple_mapping) = VALUE tt_easy_data(
FOR ls_base IN lt_base ( tkey = ls_base-tkey text = ls_base-text number = 1 )
).
Das Ergebnis der beiden Schleifen sieht wie folgt aus:
WHERE
Ähnlich wie beim LOOP haben wir auch die Möglichkeit die Daten einzuschränken und anhand der Inhalte abzugrenzen. Im folgenden Beispiel schränken wir die Daten über das Datum ein, zusätzlich haben wir noch die Möglichkeit den Index der aktuellen Zeile in der Schleife zur Verfügung zu stellen:
DATA(lt_where) = VALUE tt_easy_data(
FOR ls_base IN lt_base INDEX INTO ld_idx WHERE ( date > '20230101' )
( tkey = ls_base-tkey text = ls_base-text number = ld_idx )
).
Die Zeile übernehmen wir in den neuen Datentypen und setzen das Feld "number" auf den Index der Quellzeile, so können wir den Datensatz später noch einmal wiederfinden. Das Ergebnis nach der Schleife sieht nun wir folgt aus:
REDUCE
Als letztes Beispiel schauen wir uns einmal das Statement REDUCE an, welches Inhalte reduziert bzw. zusammenfassen soll. Auch hier ist es möglich die FOR Schleife einzusetzen. Dazu schauen wir uns einmal drei verschiedene Szenarien an und wie man sie einsetzen kann.
Zuerst einmal verwenden wir eine Zählschleife und summieren das Quadrat der Zählvariable zusammen. Dabei definieren wir mit INIT eine lokale Variable, hier kannst du verschiedene Variablen zum Rechnen und Speichern anlegen, entscheidend ist allerdings die erste Variable, deren Datentyp auf das Ergebnis mappbar sein sollte:
DATA(ld_number) = REDUCE i(
INIT num = 0
FOR i = 1 THEN i + 3 WHILE i <= 10
NEXT num = num + ( i * i )
).
Im zweiten Beispiel summieren wir alle Zahlen in unserer Basistabelle und geben das Ergebnis aus dem REDUCE zurück:
DATA(ld_sum) = REDUCE i(
INIT sum = 0
FOR ls_base IN lt_base
NEXT sum = sum + ls_base-number
).
Als letztes Beispiel rechnen wir einmal nichts zusammen, sondern setzen einen Text aus den Inhalten von Spalten aus der Tabelle zusammen. Dazu ist es wichtig, dass die Variable mit dem String Literal initialisiert wird, da sie sonst als CHAR1 definiert wird und die Verkettung nicht funktioniert:
DATA(ld_keys) = REDUCE string(
INIT keys = ``
FOR ls_base IN lt_base
NEXT keys = keys && ls_base-tkey
).
Zum Abschluss noch einmal die Ausgabe aus den drei Beispielen und wie sie in der Console aussehen würden:
Vollständiges Beispiel
Wie immer noch zum Abschluss die vollständige ausführbare Klasse, die wir für die Beispiele verwendet haben:
CLASS zcl_bs_demo_for DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES:
td_key TYPE c LENGTH 5,
BEGIN OF ts_simple_data,
tkey TYPE td_key,
text TYPE string,
number TYPE i,
date TYPE d,
END OF ts_simple_data,
tt_simple_data TYPE STANDARD TABLE OF ts_simple_data WITH EMPTY KEY,
BEGIN OF ts_easy_data,
tkey TYPE td_key,
text TYPE string,
number TYPE i,
END OF ts_easy_data,
tt_easy_data TYPE STANDARD TABLE OF ts_easy_data WITH EMPTY KEY,
tt_numbers TYPE STANDARD TABLE OF i WITH EMPTY KEY.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
generate_data
RETURNING VALUE(rt_result) TYPE tt_simple_data,
simple_for_with_counter
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out,
simple_for_with_mapping
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out,
for_with_reduce
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out,
for_with_where_condition
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out,
simple_for_with_date
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_for IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
simple_for_with_counter( out ).
simple_for_with_date( out ).
simple_for_with_mapping( out ).
for_with_reduce( out ).
for_with_where_condition( out ).
ENDMETHOD.
METHOD generate_data.
rt_result = VALUE #(
( tkey = 'A' text = `Banana` number = 14 date = '20230101' )
( tkey = 'B' text = `Tomato` number = 12 date = '20230201' )
( tkey = 'C' text = `Apple` number = 23 date = '20231201' )
( tkey = 'E' text = `Strawberry` number = 31 date = '20230101' )
( tkey = 'F' text = `Salad` number = 20 date = '20230601' )
).
ENDMETHOD.
METHOD simple_for_with_counter.
DATA(lt_numbers_until) = VALUE tt_numbers(
FOR i = 1 UNTIL i >= 10 ( i )
).
io_out->write( `Result table with NUMBERS (UNTIL):` ).
io_out->write( lt_numbers_until ).
DATA(lt_numbers_while) = VALUE tt_numbers(
FOR i = 1 WHILE i <= 10 ( i )
).
io_out->write( `Result table with NUMBERS (WHILE):` ).
io_out->write( lt_numbers_while ).
DATA(lt_numbers_then) = VALUE tt_numbers(
FOR i = 1 THEN i + 3 WHILE i <= 10 ( i )
).
io_out->write( `Result table with NUMBERS (THEN):` ).
io_out->write( lt_numbers_then ).
ENDMETHOD.
METHOD simple_for_with_mapping.
DATA(lt_base) = generate_data( ).
DATA(lt_corresponding) = VALUE tt_easy_data(
FOR ls_base IN lt_base ( CORRESPONDING #( ls_base ) )
).
io_out->write( `Result table with CORRESPONDING:` ).
io_out->write( lt_corresponding ).
DATA(lt_simple_mapping) = VALUE tt_easy_data(
FOR ls_base IN lt_base ( tkey = ls_base-tkey text = ls_base-text number = 1 )
).
io_out->write( `Result table with simple mapping:` ).
io_out->write( lt_simple_mapping ).
ENDMETHOD.
METHOD for_with_reduce.
DATA(lt_base) = generate_data( ).
DATA(ld_number) = REDUCE i(
INIT num = 0
FOR i = 1 THEN i + 3 WHILE i <= 10
NEXT num = num + ( i * i )
).
io_out->write( |Result from table reduce: { ld_number }| ).
DATA(ld_sum) = REDUCE i(
INIT sum = 0
FOR ls_base IN lt_base
NEXT sum = sum + ls_base-number
).
io_out->write( |Sum of all items: { ld_sum }| ).
DATA(ld_keys) = REDUCE string(
INIT keys = ``
FOR ls_base IN lt_base
NEXT keys = keys && ls_base-tkey
).
io_out->write( |All keys concatenated: { ld_keys }| ).
ENDMETHOD.
METHOD for_with_where_condition.
DATA(lt_base) = generate_data( ).
DATA(lt_where) = VALUE tt_easy_data(
FOR ls_base IN lt_base INDEX INTO ld_idx WHERE ( date > '20230101' )
( tkey = ls_base-tkey text = ls_base-text number = ld_idx )
).
io_out->write( `Result with date greater than 01/01/2023:` ).
io_out->write( lt_where ).
ENDMETHOD.
METHOD simple_for_with_date.
DATA(lt_date_variable) = VALUE tt_simple_data(
FOR i = CONV d( '20230101' ) THEN i + 2 WHILE i <= '20230201' ( date = i )
).
io_out->write( `Result table with date type:` ).
io_out->write( lt_date_variable ).
ENDMETHOD.
ENDCLASS.
Fazit
Schleifen mit FOR können bereits heute genutzt werden, machen aber die Lesbarkeit nicht unbedingt besser. Weiterhin sind sie kein Ersatz für LOOPs, da sie nur in vorgegebenen Statements arbeiten. Trotzdem sollte das neue Konstrukt beherrscht werden.
Quelle:
ABAP Dokumentation - FOR Iteration Expressions
ABAP Dokumentation - FOR Conditional Iteration