ABAP Deep Dive - VALUE
In diesem Artikel wollen wir uns noch einmal das Value Statement in allen Ausprägungen anschauen und wie du es in deiner täglichen Arbeit nutzen kannst.
Inhaltsverzeichnis
Bereits in einem anderen Artikel sind wir auf Aspekte des Value Statements eingegangen und wie es als neue Variante dein Leben vereinfacht. In diesem Artikel werden wir noch einmal auf die verschiedenen Varianten und Nutzungsweisen eingehen und wie die VALUE gut in deinen Alltag integrieren kannst.
Einleitung
Beim Value Statement handelt es sich um einen Konstruktor-Ausdruck, das heißt es werden neue Datentypen erzeugt. Wie bei CORRESPONDING, kann der Ausdruck genutzt werden, um die Zielvariable zu initialisieren. Value wird vor allem für Strukturen und Tabellen eingesetzt und kann an verschiedenen Positionen zum Einsatz kommen, wie bei einer Inline Deklaration, einem Insert oder bei Importing Parametern.
Vorbereitung
Bevor wir mit den Beispielen beginnen, hier noch die verschiedenen Typen, die wir gleich in unseren Beispielen verwenden. Am Ende des Artikels findest du noch einmal die komplette ausführbare Klasse, um alle Beispiele nachvollziehen zu können:
TYPES:
td_field TYPE c LENGTH 20,
tt_r_field TYPE RANGE OF td_field,
BEGIN OF ts_structure,
ident TYPE i,
text TYPE string,
last_changed TYPE timestamp,
END OF ts_structure,
tt_structure TYPE SORTED TABLE OF ts_structure WITH UNIQUE KEY ident,
tt_unsorted TYPE STANDARD TABLE OF ts_structure WITH NON-UNIQUE KEY ident.
Definition
Wenn du in deinem Quellcode bereits eine Variable definiert hast, dann kannst du Value dazu nutzen die Variable wieder zu initialisieren und mit neuen Informationen zu befüllen. Im folgenden Beispiel wurde die Variable bereits im Kopf definiert und wird nun befüllt.
DATA:
ls_predefined TYPE ts_structure.
ls_predefined = VALUE #( ident = 1 text = `Test 1` ).
Dabei kannst du nach Value den Platzhalter # verwenden, da der Typ bei der Zuweisung bekannt ist. In diesem Fall wird der Datentyp aus der Zielvariable abgeleitet. Es ist natürlich auch möglich, den Datentypen mit dem Value Statement anzugeben und den Platzhalter wegzulassen. Damit ist eine Definition der Variable per Inline-Deklaration möglich:
DATA(ls_inline) = VALUE ts_structure( ident = 2 text = `Test 2` ).
In den bisherigen Beispielen haben wir Strukturen erzeugt, es ist auch möglich auf die gleiche Weise Tabellen zu erstellen. Dazu geben wir einen Tabellentypen nach Value an und nun ist es wichtig, jeden Datensatz mit einem Paar Klammern abzugrenzen. Der Einfachheit halber unterstützen wir die Lesbarkeit, in dem wir das Statement entsprechend formatieren:
DATA(lt_inline) = VALUE tt_structure(
( ident = 3 text = `Test 3` )
( ident = 4 text = `Test 4` )
( ident = 5 text = `Test 5` )
).
Defaultwerte
Im letzten Beispiel haben wir eine Tabelle mit Zeilen befüllt, dabei haben wir in jeder Zeile alle Werte mitgegeben und so die Tabelle komplett aufgebaut. Doch wie sieht es nun eigentlich zum Beispiel mit einer Range aus? Diese können wir auf die gleiche Weise befüllen:
DATA(lt_r_full) = VALUE tt_r_field(
( sign = 'I' option = 'EQ' low = 'ABC' )
( sign = 'I' option = 'EQ' low = 'DEF' )
( sign = 'I' option = 'EQ' low = 'GHI' )
).
Was aber auffällt ist, dass wir sehr viele doppelte Einträge haben, die wir mit jeder Zeile befüllen müssen. Damit wir "SIGN" und "OPTION" nicht mit jeder Zeile befüllen müssen, können wir auch außerhalb der Zeile Defaultwerte definieren:
DATA(lt_r_default) = VALUE tt_r_field(
sign = 'I' option = 'EQ'
( low = 'ABC' )
( low = 'DEF' )
( low = 'GHI' )
).
Was nun aber nicht mehr klappt, ist es das Element anzusprechen, welches bereits außerhalb der Klammer definiert wurde. So können wir bei einer Zeile keine andere Option eintragen. Was aber funktioniert, ist den Defaultwert außerhalb zu "tauschen":
DATA(lt_r_switch) = VALUE tt_r_field(
sign = 'I' option = 'EQ'
( low = 'ABC' )
( low = 'DEF' )
option = 'BT'
( low = 'GHI' high = 'JKL' )
).
In dem gezeigten Beispiel tauschen wir nur die "OPTION" aus, "SIGN" bleibt weiterhin als "Included" definiert.
BASE
Schauen wir uns nun einmal die Verwendung des "BASE" Zusatz an und was er für die Daten bedeutet. Um das Beispiel besser zu veranschaulichen, bauen wir uns im ersten Schritt eine Grundlage auf, eine Tabelle mit drei Datensätzen:
DATA(lt_base_table) = VALUE tt_structure(
( ident = 3 text = `Test 3` )
( ident = 4 text = `Test 4` )
( ident = 5 text = `Test 5` )
).
Soweit erst einmal nichts Besonderes. Im nächsten Schritt wollen wir zwei Datensätze in die bestehenden Daten einfügen. Hier musst du beachten, dass es sich um einen Konstruktorausdruck handelt, wenn wir nun per Value noch einmal zwei Datensätze der Tabelle zuweisen, dann wird diese gelöscht und es befinden sich nur die beiden Datensätze darin. Dazu verwenden wir den Zusatz "BASE" plus die Angabe der Tabelle mit den bestehenden Datensätzen.
DATA(lt_base_extended) = VALUE tt_structure( BASE lt_base_table
( ident = 1 text = `Test 1` )
( ident = 2 text = `Test 2` )
).
ABAP kopiert die Zeilen der Basistabelle, plus die neuen Zeilen in die neu erzeugte Tabelle. Hierbei handelt es sich um eine Tabelle mit sortiertem Schlüssel, das heißt es wird der Schlüssel berücksichtigt und es kommt nicht zu einem Fehler. Anders sieht es bei dem folgenden Statement aus:
TRY.
DATA(lt_duplicate) = VALUE tt_structure( BASE lt_base_table
( ident = 1 text = `Test 1` )
).
CATCH cx_sy_itab_duplicate_key.
ENDTRY.
Wir wollen in die Tabelle, in der sich bereits die ersten 5 Datensätze befinden, einen doppelten Datensatz einfügen. In diesem Fall wird die Ausnahme CX_SY_ITAB_DUPLICATE_KEY ausgelöst und sollte abgefangen werden.
LINES OF
Der Zusatz BASE sorgte dafür, dass die Tabelle als Grundlage genommen wurde und die neuen Datensätze hinten angehangen wurden. Sollte es sich bei der Tabelle um eine sortierte Tabelle handeln, dann werden die Datensätze entsprechend ihrer Reihenfolge eingefügt. Doch wie bekommen wir nun bei einer "STANDARD" Tabelle die Datensätze zusammengesetzt? Dazu legen wir die folgende Tabelle an, dieses Mal ohne Schlüssel:
DATA(lt_base_table) = VALUE tt_unsorted(
( ident = 3 text = `Test 3` )
( ident = 4 text = `Test 4` )
( ident = 5 text = `Test 5` )
).
Im nächsten Schritt wollen wir eine neue Tabelle aufbauen und die Datensätze des vorhergehenden Schritts mit übernehmen, dieses Mal aber an einer ganz bestimmten Stelle. Dafür können wir den Zusatz "LINES OF" direkt in einer Zeile der neuen Tabelle verwenden:
DATA(lt_append_tab) = VALUE tt_unsorted(
( ident = 9 text = `Test 9` )
( ident = 7 text = `Test 7` )
( LINES OF lt_base_table )
( ident = 6 text = `Test 6` )
).
Die Zeilen werden zwischen die anderen Zeilen eingefügt und die neue Tabelle erzeugt:
Befüllung
Das VALUE Statement kann nicht nur genutzt werden, um lokale Tabellen und Strukturen zu erzeugen, sondern es kann auch mit verschiedenen Ausdrücken kombiniert werden. So kannst du es zum Beispiel auch direkt bei einem INSERT verwenden, ohne eine Zwischenvariable zu definieren:
INSERT VALUE #( ident = 10 text = `Test 10` ) INTO TABLE lt_append_tab.
Wenn eine Methode aufgerufen wird und gerade nicht die passende Variable zur Verfügung steht, kann diese auch per VALUE erzeugt werden. Diese Methode klappt auch prima bei Funktionsbausteinen, die manchmal etwas penibler auf die korrekten Datentypen reagieren:
as_parameter(
is_structure = VALUE #( ident = 10 text = `Test 10` )
).
Datentyp
Da wir auch gerade beim Thema Datentyp waren, sollten wir uns noch einfach definierte Tabellen anschauen, die keine definierten Felder haben, aber einen speziellen Datentypen voraussetzen. In den meisten Fällen werden dies Tabellen vom Typ CHAR oder STRING sein:
DATA:
lt_char TYPE STANDARD TABLE OF char25 WITH EMPTY KEY,
lt_string TYPE STANDARD TABLE OF string WITH EMPTY KEY.
Hier gibt es Kleinigkeiten bei der Befüllung zu beachten. Wenn wir nun die Tabelle mit CHAR befüllen wollen, geben wir die bisherigen Klammern an, können den Feldnamen weglassen, da es ja keinen gibt:
lt_char = VALUE #( ( 'ABC' ) ( 'DEF' ) ).
Wollen wir das Ganze jetzt für die Tabelle vom Typ STRING verwenden, erhalten wir eine entsprechende Compiler Fehlermeldung:
Hier muss bei der Befüllung der Tabelle auf die richtigen Literale geachtet werden, erst dann klappt die Verwendung dieser Methodik:
lt_string = VALUE #( ( `ABC` ) ( `DEF` ) ).
Ganzes Beispiel
Zum Abschluss des Artikels noch einmal das komplette Beispiel der Teile, die wir in diesem Artikel gezeigt haben. Weiterhin gibt es für jedes Beispiel auch noch eine Ausgabe in die Konsole, um die Ergebnisse der Operationen nachvollziehen zu können:
CLASS zcl_bs_demo_value DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES:
td_field TYPE c LENGTH 20,
tt_r_field TYPE RANGE OF td_field,
BEGIN OF ts_structure,
ident TYPE i,
text TYPE string,
last_changed TYPE timestamp,
END OF ts_structure,
tt_structure TYPE SORTED TABLE OF ts_structure WITH UNIQUE KEY ident,
tt_unsorted TYPE STANDARD TABLE OF ts_structure WITH NON-UNIQUE KEY ident.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
create_variables
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out,
default_values
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out,
base_tables
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out,
append_table
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out,
correct_types
IMPORTING
io_out TYPE REF TO if_oo_adt_classrun_out,
as_parameter
IMPORTING
is_structure TYPE zcl_bs_demo_value=>ts_structure.
ENDCLASS.
CLASS zcl_bs_demo_value IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
create_variables( out ).
default_values( out ).
base_tables( out ).
append_table( out ).
correct_types( out ).
as_parameter(
is_structure = VALUE #( ident = 10 text = `Test 10` )
).
ENDMETHOD.
METHOD create_variables.
DATA:
ls_predefined TYPE ts_structure.
ls_predefined = VALUE #( ident = 1 text = `Test 1` ).
io_out->write( `Predefined variable:` ).
io_out->write( ls_predefined ).
DATA(ls_inline) = VALUE ts_structure( ident = 2 text = `Test 2` ).
io_out->write( `Inline declaration (structure):` ).
io_out->write( ls_inline ).
DATA(lt_inline) = VALUE tt_structure(
( ident = 3 text = `Test 3` )
( ident = 4 text = `Test 4` )
( ident = 5 text = `Test 5` )
).
io_out->write( `Inline declaration (table):` ).
io_out->write( lt_inline ).
ENDMETHOD.
METHOD default_values.
DATA(lt_r_full) = VALUE tt_r_field(
( sign = 'I' option = 'EQ' low = 'ABC' )
( sign = 'I' option = 'EQ' low = 'DEF' )
( sign = 'I' option = 'EQ' low = 'GHI' )
).
io_out->write( `Range with full values:` ).
io_out->write( lt_r_full ).
DATA(lt_r_default) = VALUE tt_r_field(
sign = 'I' option = 'EQ'
( low = 'ABC' )
( low = 'DEF' )
( low = 'GHI' )
).
io_out->write( `Range with default values:` ).
io_out->write( lt_r_default ).
DATA(lt_r_switch) = VALUE tt_r_field(
sign = 'I' option = 'EQ'
( low = 'ABC' )
( low = 'DEF' )
option = 'BT'
( low = 'GHI' high = 'JKL' )
).
io_out->write( `Range with default switch:` ).
io_out->write( lt_r_switch ).
ENDMETHOD.
METHOD base_tables.
DATA(lt_base_table) = VALUE tt_structure(
( ident = 3 text = `Test 3` )
( ident = 4 text = `Test 4` )
( ident = 5 text = `Test 5` )
).
DATA(lt_base_extended) = VALUE tt_structure( BASE lt_base_table
( ident = 1 text = `Test 1` )
( ident = 2 text = `Test 2` )
).
io_out->write( `Add with base into sorted:` ).
io_out->write( lt_base_extended ).
TRY.
DATA(lt_duplicate) = VALUE tt_structure( BASE lt_base_table
( ident = 1 text = `Test 1` )
).
CATCH cx_sy_itab_duplicate_key.
io_out->write( `Duplicate key inserted` ).
ENDTRY.
ENDMETHOD.
METHOD append_table.
DATA(lt_base_table) = VALUE tt_unsorted(
( ident = 3 text = `Test 3` )
( ident = 4 text = `Test 4` )
( ident = 5 text = `Test 5` )
).
DATA(lt_append_tab) = VALUE tt_unsorted(
( ident = 9 text = `Test 9` )
( ident = 7 text = `Test 7` )
( LINES OF lt_base_table )
( ident = 6 text = `Test 6` )
).
io_out->write( `Append table into base:` ).
io_out->write( lt_append_tab ).
INSERT VALUE #( ident = 10 text = `Test 10` ) INTO TABLE lt_append_tab.
io_out->write( `After insert into base:` ).
io_out->write( lt_append_tab ).
ENDMETHOD.
METHOD as_parameter.
ENDMETHOD.
METHOD correct_types.
DATA:
lt_char TYPE STANDARD TABLE OF char25 WITH EMPTY KEY,
lt_string TYPE STANDARD TABLE OF string WITH EMPTY KEY.
lt_char = VALUE #( ( 'ABC' ) ( 'DEF' ) ).
io_out->write( `Table with CHAR base:` ).
io_out->write( lt_char ).
lt_string = VALUE #( ( `ABC` ) ( `DEF` ) ).
io_out->write( `Table with CHAR base:` ).
io_out->write( lt_char ).
ENDMETHOD.
ENDCLASS.
Fazit
Das Value Statement ist um einiges mächtiger als man im ersten Schritt denken mag. Mit den heutigen Tipps kannst du das volle Potenzial dieser Anweisung ausschöpfen und kennst die kleinen und größeren Fallstricke dabei.