[ABAP] Interne Tabellen: OPTIONAL – Leere Datensätze bei Table Expressions

TYPES: BEGIN OF ty_person,
         name TYPE string,
         age  TYPE i,
       END OF ty_person.

TYPES: ty_it_persons TYPE STANDARD TABLE OF ty_person WITH DEFAULT KEY.

DATA(it_persons) = VALUE ty_it_persons(
                                        ( name = 'Hugo' age = 40 )
                                        ( name = 'Ede' age = 65 )
                                        ( name = 'Ina' age = 35 )
                                      ).

* Datensatz für Person 'Heiner' ist nicht vorhanden -> leeren Datensatz zurückgeben
DATA(lv_p) = VALUE #( it_persons[ name = 'Heiner' ] OPTIONAL ).

WRITE: / lv_p-name.
WRITE: / lv_p-age.

* Datensatz mit Index 4 ist nicht vorhanden -> leeren Wert zurückgeben
DATA(lv_age) = VALUE #( it_persons[ 4 ]-age OPTIONAL ).

WRITE: / lv_age.

[ABAP] Interne Tabellen: DEFAULT VALUE bei Table Expressions

TYPES: BEGIN OF ty_person,
         name TYPE string,
         age  TYPE i,
       END OF ty_person.

TYPES: ty_it_persons TYPE STANDARD TABLE OF ty_person WITH DEFAULT KEY.

DATA(it_persons) = VALUE ty_it_persons(
                                        ( name = 'Hugo' age = 40 )
                                        ( name = 'Ede' age = 65 )
                                        ( name = 'Ina' age = 35 )
                                      ).

* Datensatz für Person 'Heiner' ist nicht vorhanden -> Default Datensatz <empty> zurückgeben
DATA(lv_p) = VALUE #( it_persons[ name = 'Heiner' ] DEFAULT VALUE #( name = '<empty>' age = -1 ) ).

WRITE: / lv_p-name.
WRITE: / lv_p-age.

* Datensatz mit Index 5 ist nicht vorhanden -> Default Datensatz <empty> zurückgeben
DATA(lv_i) = VALUE #( it_persons[ 5 ] DEFAULT VALUE #( name = '<empty>' age = -1 ) ).

WRITE: / lv_i-name.
WRITE: / lv_i-age.

* Datensatz mit Index 4 ist nicht vorhanden -> Default Wert für age (-1) zurückgeben
DATA(lv_age) = VALUE #( it_persons[ 4 ]-age DEFAULT -1 ).

WRITE: / lv_age.

[ABAP] Daten effizient zu internen Tabellen hinzufügen

Variante 1 (HASHED TABLE)

TYPES: BEGIN OF ty_msgnr,
         msgnr TYPE t100-msgnr,
       END OF ty_msgnr.

DATA: it_t100 TYPE HASHED TABLE OF t100 WITH UNIQUE KEY sprsl arbgb msgnr.

SELECT * FROM t100
  INTO TABLE @it_t100
  UP TO 100000 ROWS.

DATA: it_msgnr TYPE HASHED TABLE OF ty_msgnr WITH UNIQUE KEY msgnr.

DATA(o_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(usec_start) = o_timer->get_runtime( ).

LOOP AT it_t100 ASSIGNING FIELD-SYMBOL(<s>).
* jedes Element auf Vorhandensein Prüfen und ggf. zur Liste hinzufügen
  IF NOT line_exists( it_msgnr[ msgnr = <s>-msgnr ] ).
    INSERT VALUE ty_msgnr( msgnr = <s>-msgnr ) INTO TABLE it_msgnr.
  ENDIF.
ENDLOOP.

DATA(usec_end) = o_timer->get_runtime( ).
DATA(usec) = CONV decfloat16( usec_end - usec_start ).
DATA(sec) = usec / 1000000.

WRITE: / 'Laufzeit:', sec, 's'.

Variante 2 (GROUP BY)

TYPES: BEGIN OF ty_msgnr,
         msgnr TYPE t100-msgnr,
       END OF ty_msgnr.

*DATA: it_t100 TYPE HASHED TABLE OF t100 WITH UNIQUE KEY sprsl arbgb msgnr.
DATA: it_t100 TYPE STANDARD TABLE OF t100 WITH DEFAULT KEY.

SELECT * FROM t100
  INTO TABLE @it_t100
  UP TO 100000 ROWS.

DATA: it_msgnr TYPE STANDARD TABLE OF ty_msgnr WITH DEFAULT KEY.

DATA(o_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(usec_start) = o_timer->get_runtime( ).

LOOP AT it_t100 ASSIGNING FIELD-SYMBOL(<s>) GROUP BY <s>-msgnr ASCENDING.
* jedes Element auf Vorhandensein Prüfen und ggf. zur Liste hinzufügen
  APPEND VALUE ty_msgnr( msgnr = <s>-msgnr ) TO it_msgnr.
ENDLOOP.

DATA(usec_end) = o_timer->get_runtime( ).
DATA(usec) = CONV decfloat16( usec_end - usec_start ).
DATA(sec) = usec / 1000000.

WRITE: / 'Laufzeit:', sec, 's'.

Variante 3 (am schnellsten: COLLECT)

TYPES: BEGIN OF ty_msgnr,
         msgnr TYPE t100-msgnr,
       END OF ty_msgnr.

* Messages lesen
SELECT * FROM t100
  INTO TABLE @DATA(it_t100)
  UP TO 100000 ROWS.

DATA: it_msgnr TYPE STANDARD TABLE OF ty_msgnr WITH DEFAULT KEY.

DATA(o_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(usec_start) = o_timer->get_runtime( ).

LOOP AT it_t100 ASSIGNING FIELD-SYMBOL(<s>).
* jedes Element auf Vorhandensein Prüfen und ggf. zur Liste hinzufügen
* COLLECT verwendet intern HASHED TABLES
  COLLECT VALUE ty_msgnr( msgnr = <s>-msgnr ) INTO it_msgnr.
ENDLOOP.

DATA(usec_end) = o_timer->get_runtime( ).
DATA(usec) = CONV decfloat16( usec_end - usec_start ).
DATA(sec) = usec / 1000000.

WRITE: / 'Laufzeit:', sec, 's'.

[ABAP] Open SQL: WHERE-Bedingung mit Hilfe einer internen Tabelle (RANGE, STANDARD TABLE)

Variante 1 (RANGE)

* Range für MATNR definieren
TYPES: ty_rg_matnr TYPE RANGE OF matnr.

* MATNR für Suche
DATA(lv_matnr) = |12345|.

* RANGE für die Suche definieren
DATA(it_matnr) = VALUE ty_rg_matnr( ( sign   = 'I'
                                      option = 'EQ'
                                      low    = |{ lv_matnr WIDTH = 18 ALPHA = IN }|
                                      high   = '' ) ).

DATA: it_mara TYPE STANDARD TABLE OF mara WITH DEFAULT KEY.

SELECT * FROM mara INTO TABLE @it_mara WHERE matnr IN @it_matnr.

IF sy-subrc = 0.
  LOOP AT it_mara ASSIGNING FIELD-SYMBOL(<mat>).
    WRITE: / <mat>-matnr.
  ENDLOOP.
ELSE.
  WRITE: / 'Keine Daten vorhanden.'.
ENDIF.

Variante 2 (STANDARD TABLE)

* Zeilentyp für RANGE (STANDARD TABLE) für MATNR
TYPES: BEGIN OF ty_range,
         sign   TYPE ddsign,
         option TYPE ddoption,
         low    TYPE char18, " char18 für 18-stellige MATNR
         high   TYPE char18, " char18 für 18-stellige MATNR
       END OF ty_range.

* Tabellentyp für RANGE (STANDARD TABLE) für MATNR
TYPES: ty_rg_matnr TYPE STANDARD TABLE OF ty_range WITH DEFAULT KEY.

* MATNR für Suche
DATA(lv_matnr) = |12345|.

* RANGE für die Suche definieren
DATA(it_matnr) = VALUE ty_rg_matnr( ( sign   = 'I'
                                      option = 'EQ'
                                      low    = |{ lv_matnr WIDTH = 18 ALPHA = IN }|
                                      high   = '' ) ).

DATA: it_mara TYPE STANDARD TABLE OF mara WITH DEFAULT KEY.

SELECT * FROM mara INTO TABLE @it_mara WHERE matnr IN @it_matnr.

IF sy-subrc = 0.
  LOOP AT it_mara ASSIGNING FIELD-SYMBOL(<mat>).
    WRITE: / <mat>-matnr.
  ENDLOOP.
ELSE.
  WRITE: / 'Keine Daten vorhanden.'.
ENDIF.

[ABAP] Tabellentypen mit Beispielen

Übersicht interne Tabellen: Link

Beispieltyp

TYPES: BEGIN OF ty_long,
         posnr   TYPE posnr,
         matnr   TYPE matnr,
         vbeln   TYPE vbeln,
         flag_ok TYPE boolean,
       END OF ty_long.

STANDARD TABLE

* unsortiert, kann mit SORT sortiert werden
* Zugriff über Index und Schlüssel

DATA: it_st TYPE STANDARD TABLE OF ty_long WITH DEFAULT KEY.

SORTED TABLE

* nach definiertem Schlüssel sortiert
* Zugriff über Index und Schlüssel
* wenn Zugriff über Schlüssel
* schnell, da binäre Suche  

* Primary Key: posnr
* Secondary Key: flag
DATA: it_so TYPE SORTED TABLE OF ty_long WITH UNIQUE KEY posnr WITH NON-UNIQUE SORTED KEY flag COMPONENTS flag_ok.

* Primary Key: table_line
DATA: it_sorted TYPE SORTED TABLE OF string WITH UNIQUE KEY table_line.

HASHED TABLE

* Zugriff nur über eindeutigen Schlüssel, kein Index
* Zugriff nur über einen unique-Schlüssel -> jeder Schlüsselwert darf nur einmal vorkommen, sonst Exception
* schnell, wenn alle Schlüsselfelder einbezogen werden

* Primary Key: DEFAULT KEY
DATA: it_hadk TYPE HASHED TABLE OF ty_long WITH UNIQUE DEFAULT KEY.

* Primary Key: posnr
DATA: it_hauk TYPE HASHED TABLE OF ty_long WITH UNIQUE KEY posnr.

* Primary Key: DEFAULT KEY
* Secondary Key: flag 
DATA: it_hask TYPE HASHED TABLE OF ty_long WITH UNIQUE DEFAULT KEY WITH NON-UNIQUE SORTED KEY flag COMPONENTS flag_ok.

* Primary Key: posnr, matnr, vbeln
* Secondary Key: flag 
DATA: it_hauksk TYPE HASHED TABLE OF ty_long WITH UNIQUE KEY posnr matnr vbeln WITH NON-UNIQUE SORTED KEY flag COMPONENTS flag_ok.

[ABAP] Interne Tabelle mit Strings definieren

* Variante 1: unsortiert, ohne Schlüssel
DATA(it_names) = VALUE stringtab( ( |Horst| )
                                  ( |Udo| )
                                  ( |Heinz| ) ).

* Variante 2: sortiert, mit Schlüssel
TYPES: ty_sorted TYPE SORTED TABLE OF string WITH UNIQUE KEY table_line.

* strings dürfen nicht doppelt auftreten, sonst Duplicate-Key-Exception.
DATA(it_sorted) = VALUE ty_sorted( ( |Horst| )
                                   ( |Udo| )
                                   ( |Heinz| ) ).

[ABAP] RANGES

Ein RANGE definiert eine spezielle interne Tabelle vom Typ STANDARD TABLE mit Header Line zur Abbildung von Intervallen (z.B. für SELECT-OPTIONS und WHERE … IN).
Die Definition von RANGES an sich ist obsolet.

Weiterführende Infos: Link und Link

Tabellenfelder einer RANGE-Tabelle

  • SIGN (Bereichabgrenzung)
    • Typ: DDSIGN
    • Domäne: DDSIGN
    • Werte
      • I Bereichsabgrenzung eingeschlossen (Inclusive)
      • E Bereichsabgrenzung ausgeschlossen (Exclusive)
  • OPTION (Optionen)
    • Typ: DDOPTION
    • Domäne: DDOPTION
    • Werte
      • EQ gleich
      • BT zwischen … und …
      • CP enthält das Template
      • LE kleiner oder gleich
      • GE größer oder gleich
      • NE ungleich
      • NB nicht zwischen … und …
      • NP enthält das Template nicht
      • GT größer
      • LT kleiner
  • LOW (niedrigster Wert des Intervalls)
    • Typ: C
    • Länge: 1…n (abh. vom Such-Datentyp)
  • HIGH (höchster Wert des Intervalls)
    • Typ: C
    • Länge: 1…n (abh. vom Such-Datentyp)
      (leer, wenn z.B. OPTION = EQ)

DDIC-Zeilentypen, die RANGEs abbilden

* SELOPT
* RSDSSELOPT
* RSIS_S_RANGE

DDIC-Tabellentypen, die RANGEs abbilden

* RSELOPTION
* CMS_TAB_BII_CAG_TYP_RNG
* FIWTIN_T_SELOPT
* PIQ_SELOPT_T
* PSI_WE_SELOPT_TT

Hilfsklasse mit Konstanten für die Selektionstabellen bzw. Ranges-Tabellen

IF_FSBP_CONST_RANGE (Konstanten für die Selektionstabellen bzw. Ranges-Tabellen)

* OPTION_BETWEEN		'BT'
* OPTION_CONTAINS_PATTERN	'CP'
* OPTION_EQUAL			'EQ'
* OPTION_GREATER		'GT'
* OPTION_GREATER_EQUAL		'GE'
* OPTION_LESS			'LT'
* OPTION_LESS_EQUAL		'LE'
* OPTION_NOT_BETWEEN		'NB'
* OPTION_NOT_CONTAINS_PATTERN	'NP'
* OPTION_NOT_EQUAL		'NE'
* SIGN_EXCLUDE			'E'
* SIGN_INCLUDE			'I'

Beispiel 1 (Übergabe von RANGES (SELECT-OPTIONS) an Klassenmethoden)

REPORT zrange_demo.

* Klasse für Demo der Übergabe von RANGEs (SELECT-OPTIONS)
CLASS lcl_matnr DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      get_data
        IMPORTING
          i_it_matnr TYPE STANDARD TABLE. " RANGE (SELECT-OPTIONS) als STANDARD TABLE übergeben
ENDCLASS.

CLASS lcl_matnr IMPLEMENTATION.
  METHOD get_data.
    DATA: it_mara TYPE STANDARD TABLE OF mara WITH DEFAULT KEY.

* STANDARD TABLE im SQL verwenden
     SELECT * FROM mara INTO TABLE @it_mara
       WHERE matnr IN @i_it_matnr.

     ...

  ENDMETHOD.
ENDCLASS.

* SELECT-OPTIONS festlegen
DATA: lv_matnr TYPE mara-matnr.
* RANGE so_matnr für MATNR definieren
SELECT-OPTIONS: so_matnr FOR lv_matnr.

INITIALIZATION.
* RANGE so_matnr initialisieren
  so_matnr[] = VALUE #( ( sign   = 'I'
                          option = 'EQ'
                          low    = '0000012345'
                          high   = '' ) ).

START-OF-SELECTION.
* Datenbereich des RANGE so_matnr übergeben
  lcl_matnr=>get_data( i_it_matnr = so_matnr[] ).

Beispiel 2 (RANGE OF)

* erzeugt eine interne Tabelle vom Typ STANDARD TABLE mit Header Line mit den Datenfeldern
* SIGN (C(1)) | OPTION (C(2)) | LOW (C(10)) | HIGH (C(10))
DATA: it_kunnr TYPE RANGE OF kunnr.

Beispiel 3 (STANDARD TABLE)

TYPES: BEGIN OF ty_range,
         sign   TYPE ddsign,
         option TYPE ddoption,
         low    TYPE char18, " char18 für 18-stellige MATNR
         high   TYPE char18, " char18 für 18-stellige MATNR
       END OF ty_range.

* erzeugt eine interne Tabelle vom Typ STANDARD TABLE mit den Datenfeldern
* SIGN (C(1)) | OPTION (C(2)) | LOW (C(18)) | HIGH (C(18))
DATA: it_matnr TYPE STANDARD TABLE OF ty_range WITH DEFAULT KEY.

Beispiel 4 (STANDARD TABLE mit generischen DDIC-Strukturen)

DATA(rg_so_carrid) = VALUE rseloption( ( sign   = 'I'
                                         option = 'EQ'
                                         low    = 'AA'
                                         high   = '' ) ).

Beispiel 5 (Verwendung in OpenSQL-WHERE-Condition)

DATA(rg_carrid) = VALUE rseloption( ( sign = 'I' option = 'EQ' low = 'AA' high = '' )
                                    ( sign = 'I' option = 'EQ' low = 'LH' high = '' )
                                  ).

SELECT *
  INTO TABLE @DATA(it_sflight)
  FROM sflight
  WHERE carrid IN @rg_carrid.

cl_demo_output=>display( it_sflight ).

Beispiel 6 (Verwendung eines RANGESs zum Filtern von internen Tabellen)

* Liste mit Namen
DATA(it_names) = VALUE string_table( ( |Horst| ) ( |Heiner| ) ( |Ida| ) ( |Lotte| ) ( |Hanna| ) ).

* Range mit Suchbegriffen
DATA(rg_search) = VALUE rseloption( ( sign = 'I' option = 'EQ' low = 'Ida' high = '' )
                                    ( sign = 'I' option = 'EQ' low = 'Lotte' high = '' )
                                  ).

* Filtern der Liste mittels WHERE-Condition + RANGE
LOOP AT it_names ASSIGNING FIELD-SYMBOL(<n>) WHERE table_line IN rg_search.
  WRITE: / <n>.
ENDLOOP.