[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] Stringarray (interne Tabelle) zu einem String mit Separator verbinden (analog zu PHP implode)

Variante 1 (stringtab, Separator “;”)

DATA(it_strings) = VALUE stringtab( ( |Horst| )
                                    ( |Udo| )
                                    ( |Heinz| ) ).

DATA(ret) = concat_lines_of( table = it_strings sep = ';' ).

WRITE: / ret.

Variante 2 (stringtab, Separator ” “)

DATA(it_strings) = VALUE stringtab( ( |Horst| )
                                    ( |Udo| )
                                    ( |Heinz| ) ).

* Leerzeichen als Separator --> ' ' funktioniert nicht!
DATA(ret) = concat_lines_of( table = it_strings sep = | | ).

WRITE: / ret.

Variante 3 (TYPES)

TYPES: ty_names TYPE STANDARD TABLE OF string WITH EMPTY KEY.

START-OF-SELECTION.

  DATA(it_strings) = VALUE ty_names( ( |Horst| )
                                     ( |Udo| )
                                     ( |Heinz| ) ).

  DATA(ret) = concat_lines_of( table = it_strings sep = ';' ).

  WRITE: / ret.

[ABAP] Schleifenperformance durch Feldsymbole und binäre Suche verbessern

Variante 1 (langsam: keine Optimierung)

* Verkaufsbeleg: Kopfdaten
SELECT * FROM vbak INTO TABLE @DATA(it_vbak).
* Verkaufsbeleg: Positionsdaten
SELECT * FROM vbap INTO TABLE @DATA(it_vbap).

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

LOOP AT it_vbak ASSIGNING FIELD-SYMBOL(<k>).
  LOOP AT it_vbap ASSIGNING FIELD-SYMBOL(<p>) WHERE vbeln EQ <k>-vbeln.
    WRITE: / <p>-vbeln, <p>-matnr.
  ENDLOOP.
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 (schnell: FIELD-SYMBOL, BINARY SEARCH)

* Verkaufsbeleg: Kopfdaten
SELECT * FROM vbak INTO TABLE @DATA(it_vbak).
* Verkaufsbeleg: Positionsdaten
SELECT * FROM vbap INTO TABLE @DATA(it_vbap).

* Tabelle muss aufsteigend sortiert sein, sonst stimmt der Rückgabeindex bei der binären Suche nicht
SORT: it_vbak BY vbeln.
SORT: it_vbap BY vbeln.

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

LOOP AT it_vbak ASSIGNING FIELD-SYMBOL(<k>).
* binäre Suche mit READ TABLE
* -> sollen stattdessen Table-Expressions verwendet werden, muss ein Sorted Key benutzt werden
  READ TABLE it_vbap TRANSPORTING NO FIELDS WITH KEY vbeln = <k>-vbeln BINARY SEARCH.
* gefundenen Index merken
  DATA(lv_index) = sy-tabix.

  IF sy-subrc = 0.
* Suche in der sortierten Tabelle ab Index lv_index
    LOOP AT it_vbap ASSIGNING FIELD-SYMBOL(<p>) FROM lv_index WHERE vbeln = <k>-vbeln.
      WRITE: / <p>-vbeln, <p>-matnr.
    ENDLOOP.
  ELSE.
    WRITE: / 'Fehler.'.
  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 3 (am schnellsten: FIELD-SYMBOLS, HASHED TABLES, Secondary Keys)

DATA: it_vbak TYPE HASHED TABLE OF vbak WITH UNIQUE KEY vbeln.
DATA: it_vbap TYPE HASHED TABLE OF vbap WITH UNIQUE KEY vbeln posnr                             " Primary Key
                                        WITH NON-UNIQUE SORTED KEY vbeln_sort COMPONENTS vbeln. " Secondary Key

* Verkaufsbeleg: Kopfdaten
SELECT * FROM vbak INTO TABLE it_vbak.
* Verkaufsbeleg: Positionsdaten
SELECT * FROM vbap INTO TABLE it_vbap.

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

LOOP AT it_vbak ASSIGNING FIELD-SYMBOL(<k>).
* Nested Loop über Secondary Key vbeln_sort
* BINARY SEARCH wird durch die Verwendung des Secondary Keys implizit ausgeführt
  LOOP AT it_vbap ASSIGNING FIELD-SYMBOL(<p>) USING KEY vbeln_sort WHERE vbeln = <k>-vbeln.
    WRITE: / <p>-vbeln, <p>-matnr.
  ENDLOOP.
ENDLOOP.

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

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

Links

[ABAP] Objekt-Referenzen in interner Tabelle (Liste) speichern

TYPES: BEGIN OF ty_user,
         bname      TYPE usr21-bname,
         persnumber TYPE adr6-persnumber,
         addrnumber TYPE adr6-addrnumber,
         smtp_addr  TYPE adr6-smtp_addr,
       END OF ty_user.

CLASS cl_person DEFINITION.
  PUBLIC SECTION.
    METHODS: constructor
      IMPORTING
        i_userdata TYPE ty_user.
    METHODS: print_mail.
  private SECTION.
    DATA: userdata TYPE ty_user.
ENDCLASS.

CLASS cl_person IMPLEMENTATION.
  METHOD constructor.
    userdata = i_userdata.
  ENDMETHOD.

  METHOD print_mail.
    WRITE: / userdata-bname, userdata-smtp_addr.
  ENDMETHOD.
ENDCLASS.

DATA: it_userdata TYPE TABLE OF ty_user.

* interne Tabelle zur Speicherung der Objektreferenzen
DATA: it_persons TYPE TABLE OF REF TO cl_person.
* Objektreferenz
DATA: o_person TYPE REF TO cl_person.

START-OF-SELECTION.
* Daten holen
  SELECT u~bname, u~persnumber, u~addrnumber, a~smtp_addr
    INTO CORRESPONDING FIELDS OF TABLE @it_userdata
    FROM usr21 AS u
    INNER JOIN adr6 AS a ON a~persnumber = u~persnumber AND a~addrnumber = u~addrnumber
    UP TO 10 ROWS.

* Objekte erzeugen und an Referenztabelle anhängen
  LOOP AT it_userdata INTO DATA(wa_user).
    o_person = new cl_person( wa_user ).
    APPEND o_person TO it_persons.
  ENDLOOP.

* Referenztabelle durchloopen und Objekte aufrufen
  LOOP AT it_persons INTO o_person.
    o_person->print_mail( ).
  ENDLOOP.

[ABAP] Generische Verarbeitung von internen Tabellen / Verwendung von Referenzen

* irgendeine interne Tablelle anlegen
DATA: it_tab TYPE STANDARD TABLE OF ...
* generische Tabellenreferenz anlegen
DATA: o_ref_table TYPE REF TO data.
 
...
 
* Tabellenreferenz übergeben
o_ref_table = REF #( it_tab ).
 
* Referenz auf generisches Feldsymbol mappen
FIELD-SYMBOLS: <tab> TYPE ANY TABLE.
ASSIGN o_ref_table->* TO <tab>.
 
* Tabellen-Zeile erzeugen
* muss hier erfolgen, damit man ein "greifbares" Tabellen-Zeilen-Objekt
* für die Strukturermittlung (describe_by_data) hat
DATA: o_row TYPE REF TO data.
CREATE DATA o_row LIKE LINE OF <tab>.
FIELD-SYMBOLS: <row> TYPE any.
ASSIGN o_row->* TO <row>.
 
* Komponenten (Spalten) einer Tabellenzeile ermitteln
DATA(o_struct) = CAST cl_abap_structdescr( cl_abap_typedescr=>describe_by_data( <row> ) ).
DATA(it_comp_tab) = o_struct->get_components( ).
 
* Anzahl Spalten der Tabellen-Zeile holen
DATA(lv_colcnt) = lines( it_comp_tab ).
 
* Tabelle durchloopen
LOOP AT <tab> ASSIGNING <row>.

* Spalten der akt. Zeile durchgehen
  DO lv_colcnt TIMES.
* Zelle: n-tes Element der akt. Zeile holen
    ASSIGN COMPONENT sy-index OF STRUCTURE <row> TO FIELD-SYMBOL(<cell>).
    
* Trennzeichen vor die Spalte einfügen, wenn nicht 1. Spalte
    IF sy-index > 1.
      WRITE: '|'.
    ENDIF.

* Achtung: Zell-Typ beachten! Es können hier nur flache Typen (Keine Strukturen, Tabellen) ausgegeben werden, der Rest muss gesondert behandelt werden
    IF CAST cl_abap_elemdescr( it_comp_tab[ sy-index ]-type )->kind = cl_abap_elemdescr=>kind_elem.
* Ausgabe Zellinhalt mit vordefinierter Spaltenbreite
      WRITE: |{ <cell> WIDTH = 20 |.
    ENDIF.
  ENDDO.

  NEW-LINE.
ENDLOOP.

[ABAP] Interne Tabelle als Datei (CSV) auf den lokalen Rechner speichern

DATA: itab TYPE STANDARD TABLE OF string.
DATA: lv_file TYPE string VALUE 'c:\temp\test.txt'.

...
 
TRY.
  cl_gui_frontend_services=>gui_download(
    EXPORTING
      filename = lv_file
      filetype = 'DAT'  " mögliche Filetypen: BIN, ASC, DAT, DBF, WK1, VSS, IBM
      codepage = '4110' " UTF-8 Encoding: 4110, UTF-16 Big Endian : 4102, UTF-16 Little Endian : 4103.    
    CHANGING
      data_tab = itab ).
  CATCH cx_root INTO DATA(e_text).
    MESSAGE e_text->get_text( ) TYPE 'I'.
ENDTRY.

Weiterführende Infos: Link

[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.