[ABAP] Vergleich von LOOPs mit Objektreferenzen und Feldsymbolen

* http://zevolving.com/2014/03/use-of-reference-variable-vs-workarea-vs-field-symbols/
DATA(it_p) = VALUE stringtab( ( |Udo| ) ( |Ede| ) ( |Ida| ) ).

* Code mit Referenz -> langsamer als ASSIGNING FIELD-SYMBOL
LOOP AT it_p REFERENCE INTO DATA(o_p).
* o_p dereferenzieren und Wert ausgeben
  WRITE: / o_p->*.
ENDLOOP.

* Code mit Feldsymbol -> schneller als REFERENCE INTO
LOOP AT it_p ASSIGNING FIELD-SYMBOL(<p>).
  WRITE: / <p>.
ENDLOOP.

[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] OpenSQL: Vergleich von SELECT-Abfragen über RANGES, FOR ALL ENTRIES, JOINS

Allgemein

In den folgenden Beispielen werden drei der gängigen Datenbankzugriffe über ein SELECT nach Laufzeit sowie Vor- und Nachteilen verglichen.

Variante 1 (RANGE)

* Laufzeit: 0,086142s

* Vorteile:
*   - Aufteilen und Manipulieren von verknüpften DB-Abfragen
*   - Abfrage in einem Block -> gut für kleine Abfragen mit wenigen RANGE-Einträgen
*   - Verwendung z.B. bei Nutzung von SELECT-OPTIONS
* Nachteile:
*   - Laufzeitverhalten bei großen Abfragen
*   - keine Abhängigkeiten von zwei oder mehr Feldern in einem RANGE abbildbar (Verknüpfung in der WHERE-Clause erfolgt nur einzeln über AND/OR)
*   - RANGES werden beim SELECT in ein Statement mit vielen ORs umgewandelt
*     -> abhängig vom DB-System kommt es früher oder später zu DUMPS, wenn der RANGE zu viele Einträge beinhaltet (Speichergröße der RANGE-Table > 64kB)
DATA: lv_maktx TYPE maktx VALUE '<empty>'.

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

* SELECT 1: Materialnummern
SELECT m~matnr, @lv_maktx AS maktx
  INTO TABLE @DATA(it_mara)
  FROM mara AS m
  UP TO 1000 ROWS.

DATA: rg_matnr TYPE RANGE OF matnr.

* MATNR in RANGE kopieren, darauf achten, dass die Suchfelder UNIQUE sind
rg_matnr = VALUE #( FOR <m> IN it_mara
                    (
                      sign   = 'I'
                      option = 'EQ'
                      low    = <m>-matnr
                      high   = ''
                    ) ).

* SELECT 2: Kurztexte
SELECT t~matnr, t~maktx
  INTO TABLE @DATA(it_makt)
  FROM makt AS t
  WHERE t~matnr IN @rg_matnr
    AND t~spras = @sy-langu.

* Zuweisung MATNR, MAKTX
LOOP AT it_mara ASSIGNING FIELD-SYMBOL(<t>).
  IF line_exists( it_makt[ matnr = <t>-matnr ] ).
    <t>-maktx = it_makt[ matnr = <t>-matnr ]-maktx.
  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'.

cl_demo_output=>display( it_mara  ).

Variante 2 (FOR ALL ENTRIES)

* Laufzeit: 0,073252s

* Vorteile:
*   - Aufteilen und Manipulieren von verknüpften DB-Abfragen
*   - mehrere FOR ALL ENTRIES können pro Statement verwendet werden
*   - Verwendung z.B. bei Verarbeitung von vielen Datensätzen
* Nachteile:
*   - Laufzeitverhalten bei großen Abfragen
*   - wenn die FOR ALL ENTRIES Liste leer ist, wir die WHERE-Clause ignoriert, was zu unerwünschten Nebeneffekten führen kann
*   - FOR ALL ENTRIES werden beim SELECT in viele einzelne Statements umgewandelt
*   - man sollte darauf achten, dass die Einträge in der FOR ALL ENTRIES-Tabelle nur einmal auftreten -> Performance
*   - Speicherverbrauch durch die vielen Abfragen
* Anmerkungen:
*   - doppelte Einträge in der FOR ALL ENTRIES Tabelle werden entfernt (SELECT DISTINCT)
*   - wichtige Parameter für die Performance von FOR ALL ENTRIES sind:
*     rsdb/max_blocking_factor
*     rsdb/min_blocking_factor
*     rsdb/max_in_blocking_factor
*     rsdb/min_in_blocking_factor
*     rsdb/prefer_in_itab_opt
*     rsdb/prefer_fix_blocking
DATA: lv_maktx TYPE maktx VALUE '<empty>'.

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

* SELECT 1: Materialnummern
* darauf achten, dass die Einträge in der FOR ALL ENTRIES Tabelle UNIQUE sind
SELECT m~matnr, @lv_maktx AS maktx
  INTO TABLE @DATA(it_mara)
  FROM mara AS m
  UP TO 1000 ROWS.

* Prüfung auf IS INITIAL ist wichtig, da sonst die WHERE-Clause im SELECT ignoriert würde
IF NOT it_mara IS INITIAL.

* SELECT 2: Kurztexte
  SELECT t~matnr, t~maktx
    INTO TABLE @DATA(it_makt)
    FROM makt AS t
    FOR ALL ENTRIES IN @it_mara
    WHERE t~matnr = @it_mara-matnr
      AND t~spras = @sy-langu.

* Zuweisung MATNR, MAKTX
  LOOP AT it_mara ASSIGNING FIELD-SYMBOL(<m>).
    IF line_exists( it_makt[ matnr = <m>-matnr ] ).
      <m>-maktx = it_makt[ matnr = <m>-matnr ]-maktx.
    ENDIF.
  ENDLOOP.
ENDIF.

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

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

cl_demo_output=>display( it_mara  ).

Variante 3 (JOIN)

* Laufzeit: 0,012196s

* Vorteile:
*   - wesentlich schneller als RANGE und FOR ALL ENTRIES
*   - eine komplexe DB-Anfrage (generisch)
*   - Verwendung bei Verarbeitung von vielen Datensätzen verteilt über viele Tabellen
* Nachteile:
*   - komplexe Statements
*   - schlecht zu Debuggen
DATA(o_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(usec_start) = o_timer->get_runtime( ).

* SELECT: Materialnummern, Kurztexte
SELECT m~matnr, t~maktx
  INTO TABLE @DATA(it_mara)
  FROM mara AS m
  RIGHT OUTER JOIN makt AS t ON ( m~matnr = t~matnr )
  UP TO 1000 ROWS
  WHERE t~spras = @sy-langu.

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

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

cl_demo_output=>display( it_mara  ).

Links

[ABAP] REDUCE: Minimum / Maximum in einer internen Tabelle suchen

* Maximum
DATA(lv_max) = 100.

* Zufallszahlen 1 ... lv_max
DATA(o_rand_i) = cl_abap_random_int=>create( seed = cl_abap_random=>seed( ) min = 1 max = lv_max ).

TYPES: ty_it_rand TYPE STANDARD TABLE OF i WITH DEFAULT KEY.

* Tabelle mit Zufallswerten
DATA(it_rand) = VALUE ty_it_rand( FOR v = 1 WHILE v < 11
                                  ( o_rand_i->get_next( ) ) ).

* Datenausgabe
LOOP AT it_rand ASSIGNING FIELD-SYMBOL(<r>).
  WRITE: / sy-tabix, ':', <r>.
ENDLOOP.

* Minimumsuche
DATA(min) = REDUCE i( INIT m = lv_max
                      FOR <v> IN it_rand
                      NEXT m = COND #( WHEN <v> < m THEN <v> ELSE m ) ).

WRITE: / 'Min:', min.

* Maximumsuche
DATA(max) = REDUCE i( INIT m = 0
                      FOR <v> IN it_rand
                      NEXT m = COND #( WHEN <v> > m THEN <v> ELSE m ) ).

WRITE: / 'Max:', max.

[ABAP] REDUCE: Spaltensumme eines Feldes einer internen Tabelle berechnen

DATA: it_spfli TYPE STANDARD TABLE OF spfli.

* Daten holen
SELECT * FROM spfli INTO TABLE it_spfli.

* Daten ausgeben
LOOP AT it_spfli ASSIGNING FIELD-SYMBOL(<f>).
  WRITE: / <f>-carrid, <f>-connid, <f>-fltime.
ENDLOOP.

* Gesamtflugzeit berechnen (Summe)
DATA(lv_fltime_sum) = REDUCE spfli-fltime( INIT s = 0
                                           FOR <l> IN it_spfli
                                           NEXT s = s + <l>-fltime ).

* Gesamtflugzeit ausgeben
WRITE: / '--> Gesamtflugzeit:', lv_fltime_sum.

[ABAP] REDUCE: Anzahl bestimmter Datensätze in einer internen Tabelle zählen

DATA: it_spfli TYPE STANDARD TABLE OF spfli.

* Daten holen
SELECT * FROM spfli INTO TABLE it_spfli.

* Daten ausgeben
LOOP AT it_spfli ASSIGNING FIELD-SYMBOL(<fs_spfli>).
  WRITE: / <fs_spfli>-carrid, <fs_spfli>-connid, <fs_spfli>-airpfrom, <fs_spfli>-airpto.
ENDLOOP.

* Datensätze für 'LH' zählen
DATA(lv_cnt_lh) = REDUCE i( INIT x = 0
                            FOR <l> IN it_spfli WHERE ( carrid = 'LH' )
                            NEXT x = x + 1 ).

* Anzahl 'LH'-Flüge ausgeben
WRITE: / '--> Anzahl LH-Flüge:', lv_cnt_lh.

[ABAP] Parallelverarbeitung / Multitasking

Auch SAP kann Multitasking. Dazu muss mann einen RFC-fähigen Funktionsbaustein in einem neuen Task innerhalb einer sogenannten Taskgroup aufrufen. Die Abarbeitung erfolgt dann asynchron, dass Ergebnis wird in einer Handler-Funktion zurückgeliefert.

Im folgenden Funktionsbeispiel erzeugt das Rahmenprogramm “zparallel” Arbeitspakete (it_input) für die Tasks und ein Objekt der Klasse “lcl_task_manager”. Letzteres managed die Verteilung der Eingangsdaten (it_input) auf mehrere Tasks. Die Taskobjekte werden durch die Klasse “lcl_task” abgebildet. Die einzelnen Tasks rufen asynchron mit ihren Arbeitspaketen den RFC-fähigen Funktionsbaustein “BAPI_BILLINGDOC_GETLIST”.

Nach erfolgreichem Beenden aller Tasks werden die Ergebnisse der Arbeitspakete zusammengesetzt (it_result) und an das Rahmenprogramm zurückgegeben.

Weiterführende Informationen: Link, Link, Link und Link

Rahmenprogramm: REPORT zparallel (Aufruf der Parallelverarbeitung)

REPORT zparallel.

* Klassen für Taskmanagement
INCLUDE ztask_manager.

* iTabs für Eingabe- und Ausgabedaten
DATA: it_input TYPE lcl_task=>ty_it_input_data.
DATA: it_result TYPE lcl_task_manager=>ty_it_final_result.
DATA: it_statistic TYPE lcl_task_manager=>ty_it_statistic.

PARAMETERS: p_rows TYPE i DEFAULT 1000. " Test-Elemente
PARAMETERS: p_wait TYPE i DEFAULT 5.    " Wartezeit in [s]
PARAMETERS: p_max_rt TYPE i DEFAULT 3.  " max. Anzahl Versuche
PARAMETERS: p_size TYPE i DEFAULT 100.  " Intervallgröße

START-OF-SELECTION.

  DATA: it_vbelv TYPE STANDARD TABLE OF vbelv WITH DEFAULT KEY.

* Intervalle erzeugen
  SELECT DISTINCT vbelv FROM vbfa INTO TABLE @it_vbelv UP TO @p_rows ROWS.

  IF sy-subrc = 0.
    SORT: it_vbelv BY table_line.

    DATA(lv_lines) = lines( it_vbelv ).
    DATA(lv_parts) = lv_lines DIV p_size.
    DATA(lv_mod)   = lv_lines MOD p_size.
    IF lv_mod <> 0.
      lv_parts = lv_parts + 1.
    ENDIF.

    DATA(lv_idx_l) = 0.
    DATA(lv_idx_h) = 0.

    DO lv_parts TIMES.
      lv_idx_l = lv_idx_h + 1.
      lv_idx_h = lv_idx_l - 1 + p_size.

      IF lv_idx_h > lv_lines.
        lv_idx_h = lv_lines.
      ENDIF.

      APPEND VALUE #( sign         = 'I'
                      option       = 'BT'
                      ref_doc_low  = it_vbelv[ lv_idx_l ]
                      ref_doc_high = it_vbelv[ lv_idx_h ] ) TO it_input.
    ENDDO.

* Referenz auf Tastmanager für Parallelverarbeitung
    DATA(o_task_manager) = lcl_task_manager=>get_instance( i_task_group = lcl_task_manager=>co_default_task_group ).

    DATA: lv_started TYPE i.
    DATA: lv_ended TYPE i.
    DATA: lv_missed TYPE i.

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

* Parallele Verarbeitung der Eingabedaten starten
    IF abap_true = o_task_manager->start_parallel_working( EXPORTING
                                                             i_it_input_data = it_input
                                                             i_wait_time_sec = p_wait
                                                             i_max_retries   = p_max_rt
                                                           IMPORTING
                                                             e_it_final_result = it_result
                                                             e_it_statistic    = it_statistic
                                                             e_tasks_started   = lv_started
                                                             e_tasks_ended     = lv_ended
                                                             e_tasks_missed    = lv_missed ).

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

      WRITE: / '    Zeit [µs]:', usec.
      WRITE: / '     Zeit [s]:', sec.
      WRITE: / '   max. Tasks:', o_task_manager->get_max_tasks( ).
      WRITE: / '   free Tasks:', o_task_manager->get_free_tasks( ).
      WRITE: / 'Tasks started:', lv_started.
      WRITE: / '  Tasks ended:', lv_ended.
      WRITE: / ' Tasks missed:', lv_missed.

      WRITE: / '  Input lines:', lines( it_input ).
      WRITE: / ' Result lines:', lines( it_result ).
      ULINE.
      WRITE: / | Task #     \| Retries     \| Time [µs]  \| S \| IN: LOW    \| IN: HIGH   \| SUBRC  \| SIZE|.
      ULINE.
      LOOP AT it_statistic ASSIGNING FIELD-SYMBOL(<fs_stat>).
        WRITE: / <fs_stat>-task_number, '|', <fs_stat>-retries, '|', <fs_stat>-runtime, '|', <fs_stat>-successful, '|', <fs_stat>-input_data-ref_doc_low, '|', <fs_stat>-input_data-ref_doc_high, '|', <fs_stat>-subrc, '|', <fs_stat>-output_part_size.
      ENDLOOP.

      ULINE.
      WRITE: / | ITEM #     \| IN: LOW    \| IN: HIGH   \| OUT: REF_DOC|.
      ULINE.
* Ergebnisausgabe
      LOOP AT it_result ASSIGNING FIELD-SYMBOL(<fs_res>).
        WRITE: / sy-tabix, '|', <fs_res>-input_data-ref_doc_low, '|', <fs_res>-input_data-ref_doc_high, '|', <fs_res>-output_data-ref_doc.
      ENDLOOP.
    ENDIF.
  ENDIF.

Task-Klassen: INCLUDE ZTASK_MANAGER

*--------------------------------------------------------------------*
* Task-Klasse (Worker)
*--------------------------------------------------------------------*
* Ruft asynchon einen RFC-fähigen Funktionsbaustein
* und verarbeitet asynchron das Ergebnis (Event)
*--------------------------------------------------------------------*
CLASS lcl_task DEFINITION FINAL.
  PUBLIC SECTION.

* Eingabedaten des Task
    TYPES: ty_input_data TYPE bapi_ref_doc_range.
* Ausgabedaten des Task
    TYPES: ty_output_data TYPE bapivbrksuccess.

    TYPES: ty_it_input_data TYPE STANDARD TABLE OF ty_input_data WITH DEFAULT KEY.
    TYPES: ty_it_output_data TYPE STANDARD TABLE OF ty_output_data WITH DEFAULT KEY.

* Name des RFC-fähigen Funktionsbausteines für "start_async_call" und "task_receive"-Handler
    CONSTANTS: co_fb_name TYPE string VALUE 'BAPI_BILLINGDOC_GETLIST'.

* Event, wird getriggert, wenn die asynchrone Methode task_receive aufgerufen wird
    EVENTS:
      data_received
        EXPORTING
          VALUE(e_subrc) TYPE sy-subrc
          VALUE(e_runtime) TYPE int4
          VALUE(e_retries) TYPE i
          VALUE(e_input_data) TYPE ty_input_data
          VALUE(e_output_data) TYPE ty_it_output_data.

* Konstruktor
    METHODS:
      constructor
        IMPORTING
          i_task_group  TYPE rzllitab-classname
          i_task_number TYPE i.

* Startet asynchronen Aufruf des RFC-Bausteins
    METHODS:
      start_async_call
        IMPORTING
                  i_input_data              TYPE ty_input_data
                  i_started_after_n_retries TYPE i
        RETURNING VALUE(rv_subrc)           TYPE sy-subrc.

* Handlermethode, wird asynchron gerufen
    METHODS:
      task_receive
        IMPORTING
          p_task TYPE clike.

* Hilfsfunktionen
    METHODS:
      is_running
        RETURNING VALUE(rv_running) TYPE boolean.

    METHODS:
      get_group
        RETURNING VALUE(rv_group) TYPE rzllitab-classname.

    METHODS:
      get_task_number
        RETURNING VALUE(rv_number) TYPE i.

  PRIVATE SECTION.

    DATA: lv_group TYPE rzllitab-classname.
    DATA: lv_number TYPE i.
    DATA: lv_running TYPE boolean.
    DATA: lv_started_after_n_retries TYPE i.

    DATA: lv_start_time TYPE int4.
    DATA: lv_end_time TYPE int4.

* Merker für Übergabe-Parameter in start_async_call
    DATA: lv_input_data TYPE ty_input_data.
ENDCLASS.

CLASS lcl_task IMPLEMENTATION.
*--------------------------------------------------------------------*
* Konstruktor
*--------------------------------------------------------------------*
* -> i_task_group  - Gruppenname
* -> i_task_number - Tasknummer
*--------------------------------------------------------------------*
  METHOD constructor.
    lv_group                   = i_task_group.
    lv_number                  = i_task_number.
    lv_running                 = abap_false.
    lv_start_time              = 0.
    lv_end_time                = 0.
    lv_started_after_n_retries = 0.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Startet asynchronen Aufruf des RFC-Bausteins
*--------------------------------------------------------------------*
* -> i_input_data              - Eingabedaten für Verarbeitung
* -> i_started_after_n_retries - Statistikwert, nach wieviel Versuchen
*                                der Task gestartet werden konnte
* <- rv_subrc                  - Status des asynchronen Aufrufs
*--------------------------------------------------------------------*
  METHOD start_async_call.

    lv_running                 = abap_false.
    lv_started_after_n_retries = i_started_after_n_retries.

    GET RUN TIME FIELD lv_start_time.

* neuen asynchronen Aufruf des Bausteines starten
* Gruppe, Tasknummer und Handlerfunktion übergeben
* Baustein muss RFC-fähig sein
* Verfügbare RFC-Gruppen für Parallelverarbeitung: Transaktion RZ12
* Namen und Typen der EXPORTING-Parameter beachten:
* -> fehlerhafte Angaben werden nicht vom Compiler bemerkt oder auch nicht als sy-subrc zurückgegeben
    CALL FUNCTION co_fb_name
      STARTING NEW TASK lv_number
      DESTINATION IN GROUP lv_group
      CALLING task_receive ON END OF TASK
      EXPORTING
        refdocrange           = i_input_data
      EXCEPTIONS
        system_failure        = 1
        communication_failure = 2
        resource_failure      = 3
        OTHERS                = 4.

* Fehlerstatus zurückgeben
    rv_subrc = sy-subrc.

    IF rv_subrc = 0.
      lv_input_data      = i_input_data.
      lv_running         = abap_true.
    ENDIF.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Receive-Funktion, welche bei Fertigsstellung vom Task asynchron
* getriggert wird
* Es werden keine WRITE-Statements ausgeführt!
*--------------------------------------------------------------------*
* -> p_task            - Tasknummer
*--------------------------------------------------------------------*
  METHOD task_receive.

    DATA: it_task_data TYPE ty_it_output_data.

* Ergebnisse des asynchronen RFC-Aufrufes vom Funktionsbaustein holen
    RECEIVE RESULTS FROM FUNCTION co_fb_name
      TABLES
        success               = it_task_data
      EXCEPTIONS
        system_failure        = 1
        communication_failure = 2
        resource_failure      = 3
        OTHERS                = 4.

    GET RUN TIME FIELD lv_end_time.

    DATA(lv_runtime) = lv_end_time - lv_start_time.

* Event für Datenrückgabe triggern
    RAISE EVENT data_received
      EXPORTING
        e_subrc       = sy-subrc
        e_runtime     = lv_runtime
        e_retries     = lv_started_after_n_retries
        e_input_data  = lv_input_data
        e_output_data = it_task_data.

    lv_running = abap_false.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Status des Tasks
*--------------------------------------------------------------------*
* <- rv_running - Status des Tasks
*--------------------------------------------------------------------*
  METHOD is_running.
    rv_running = lv_running.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Gruppenname, in welcher der Task läuft
*--------------------------------------------------------------------*
* <- rv_group - Gruppenname
*--------------------------------------------------------------------*
  METHOD get_group.
    rv_group = lv_group.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Tasknummer
*--------------------------------------------------------------------*
* <- rv_number - Tasknummer
*--------------------------------------------------------------------*
  METHOD get_task_number.
    rv_number = lv_number.
  ENDMETHOD.
ENDCLASS.
*--------------------------------------------------------------------*
* Verwaltungs-Klasse für Tasks
*--------------------------------------------------------------------*
* 1. Verarbeitet Eingabedaten (Workitems)
* 2. Holt für jedes Workitem einen freien Task
* 3. Startet pro Workitem einen Task (asynchron)
* 4. Wertet das asynchron gelieferte Ergebis in einer Handlermethode aus
*--------------------------------------------------------------------*
* Sinnvoll ist die Implementierung nur, wenn die Workprozesse
* ausreichend lange dauern, so dass sich eine parallele Verarbeitung
* überhaupt lohnt
*--------------------------------------------------------------------*
CLASS lcl_task_manager DEFINITION FINAL CREATE PRIVATE.
  PUBLIC SECTION.

* Datentyp für finale Ausgabe, wenn alle Workprozesse fertig sind
    TYPES: BEGIN OF ty_final_result,
             input_data  TYPE lcl_task=>ty_input_data,
             output_data TYPE lcl_task=>ty_output_data,
           END OF ty_final_result.

    TYPES: ty_it_final_result TYPE STANDARD TABLE OF ty_final_result WITH DEFAULT KEY.

    TYPES: BEGIN OF ty_statistic,
             task_number      TYPE i,
             runtime          TYPE int4,
             retries          type i,
             input_data       TYPE lcl_task=>ty_input_data,
             output_part_size TYPE i,
             subrc            TYPE sy-subrc,
             successful       TYPE boolean,
           END OF ty_statistic.

    TYPES: ty_it_statistic TYPE STANDARD TABLE OF ty_statistic WITH DEFAULT KEY.

* RFC-Gruppenname
* die Gruppe mit classname = 'parallel_generators' wird standardmäßig von Admins eingerichtet
* Gruppen sind in Tabelle "RZLLITAB" abgelegt, grouptype = 'S' (Server)
* Transaktion RZ12
    CONSTANTS: co_default_task_group TYPE rzllitab-classname VALUE 'parallel_generators'.

* Factory-Funktion
    CLASS-METHODS:
      get_instance
        IMPORTING
                  i_task_group               TYPE rzllitab-classname DEFAULT co_default_task_group
        RETURNING VALUE(rv_ref_task_manager) TYPE REF TO lcl_task_manager.

* Startet die parallele Abarbeitung der Eingabedaten
    METHODS:
      start_parallel_working
        IMPORTING
                  i_it_input_data         TYPE lcl_task=>ty_it_input_data
                  i_wait_time_sec         TYPE i DEFAULT 10
                  i_max_retries           TYPE i DEFAULT 3
        EXPORTING
                  e_it_final_result       TYPE ty_it_final_result
                  e_it_statistic          TYPE ty_it_statistic
                  e_tasks_started         TYPE i
                  e_tasks_ended           TYPE i
                  e_tasks_missed          TYPE i
        RETURNING VALUE(rv_work_complete) TYPE boolean.

    METHODS:
      get_max_tasks
        RETURNING VALUE(rv_max_tasks) TYPE i.

    METHODS:
      get_free_tasks
        RETURNING VALUE(rv_free_tasks) TYPE i.

  PRIVATE SECTION.

    TYPES: BEGIN OF ty_task,
             task_number TYPE i,
             o_task      TYPE REF TO lcl_task,
           END OF ty_task.

* Null-Referenz
    CONSTANTS: null TYPE REF TO lcl_task_manager VALUE IS INITIAL.

* Referenz auf ein Task-Objekt
    CLASS-DATA: o_task_manager TYPE REF TO lcl_task_manager.

    CLASS-DATA: lv_max_tasks TYPE i.
    CLASS-DATA: lv_free_tasks TYPE i.

* Zähler
    CLASS-DATA: lv_tasks_started TYPE i.
    CLASS-DATA: lv_tasks_ended TYPE i.
    CLASS-DATA: lv_active_tasks TYPE i.

* globale Taskliste
    CLASS-DATA: it_tasks TYPE SORTED TABLE OF ty_task WITH UNIQUE KEY task_number.

* Ergebnisliste
    CLASS-DATA: it_final_result TYPE ty_it_final_result.

* Ergebnisliste
    CLASS-DATA: it_statistic TYPE ty_it_statistic.

* Handler für data_received-Ereignis der Tasks
    METHODS:
      on_task_completed FOR EVENT data_received OF lcl_task
        IMPORTING
            e_subrc
            e_runtime
            e_retries
            e_input_data
            e_output_data
            sender.

* Hilfsfunktionen
    METHODS:
      get_free_task
        RETURNING VALUE(rv_task_number) TYPE i.

    METHODS:
      get_task
        IMPORTING
                  i_task_number      TYPE i
        RETURNING VALUE(rv_ref_task) TYPE REF TO lcl_task.

    METHODS:
      start_task
        IMPORTING
                  i_input_data    TYPE lcl_task=>ty_input_data
                  i_wait_time_sec TYPE i DEFAULT 10
                  i_max_retries   TYPE i DEFAULT 3
        RETURNING VALUE(rv_ok)    TYPE boolean.
ENDCLASS.
*--------------------------------------------------------------------*
CLASS lcl_task_manager IMPLEMENTATION.
*--------------------------------------------------------------------*
* Factory Methode für Singleton (genau eine Referenz) auf den
* Taskmanager zurück
*--------------------------------------------------------------------*
* -> i_task_group        - Name der RFC-Task-Group, in der die Tasks ablaufen
*                          sollen
*                          Vorgabewert ist 'parallel_generators', welche
*                          standardmäßig von Admins eingerichtet wird
*                          Gruppen sind in Tabelle "RZLLITAB" abgelegt,
*                          grouptype = 'S' (Server)
*                          Transaktion RZ12
* <- rv_ref_task_manager - Referenz auf Task-Manager-Objekt
*--------------------------------------------------------------------*
  METHOD get_instance.

    rv_ref_task_manager = null.

    IF o_task_manager IS INITIAL.

      DATA(lv_task_group) = i_task_group.

      lv_max_tasks = 0.
      lv_free_tasks = 0.

      IF lv_task_group IS INITIAL.
* falls keine task group übergeben wurde, eine heraussuchen
* Verfügbare RFC-Gruppen für Parallelverarbeitung: Transaktion RZ12
        SELECT SINGLE classname FROM rzllitab INTO @lv_task_group WHERE grouptype = 'S'.
      ENDIF.

      IF NOT lv_task_group IS INITIAL.

* Start neue LUW
        COMMIT WORK.

* Parallel Background Tasks - Initialisierung der PBT-Umgebung
* Sollte nur 1x aufgerufen werden, sonst Exception "pbt_env_already_initialized"
        CALL FUNCTION 'SPBT_INITIALIZE'
          EXPORTING
            group_name                     = lv_task_group
          IMPORTING
            max_pbt_wps                    = lv_max_tasks
            free_pbt_wps                   = lv_free_tasks
          EXCEPTIONS
            invalid_group_name             = 1
            internal_error                 = 2
            pbt_env_already_initialized    = 3
            currently_no_resources_avail   = 4
            no_pbt_resources_found         = 5
            cant_init_different_pbt_groups = 6
            OTHERS                         = 7.

* wenn Initialisierung ok
        IF sy-subrc = 0.
* wenn genug freie Tasks vorhanden
          IF lv_free_tasks >= 1.

            o_task_manager = NEW #( ).

            lv_tasks_started = 0.
            lv_tasks_ended   = 0.
            lv_active_tasks  = 0.

            CLEAR: it_tasks.

* Anzahl freie Tasks zur Liste hinzufügen
            DO lv_free_tasks TIMES.
              DATA(o_task) = NEW lcl_task( i_task_number = sy-index
                                           i_task_group  = lv_task_group ).

* Eventhandler für beendete Tasks
              SET HANDLER o_task_manager->on_task_completed FOR o_task.

              APPEND VALUE #( task_number = sy-index
                              o_task      = o_task ) TO it_tasks.
            ENDDO.

            rv_ref_task_manager = o_task_manager.
          ENDIF.

        ENDIF.

      ENDIF.

    ELSE.
      rv_ref_task_manager = o_task_manager.
    ENDIF.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Parallele / asynchrone Abarbeitung anschieben
*--------------------------------------------------------------------*
* -> i_it_input_data   - Eingabedaten
* -> i_wait_time_sec   - Wartezeit für Abarbeitung aller Tasks in sec
* -> i_max_retries     - max. Anz. Versuche
* <- e_it_final_result - Erbebnismenge der parallelen Verarbeitung
* <- e_tasks_started   - Anz. gestartete Tasks
* <- e_tasks_ended     - Anz. beendete Tasks (sollte im Idealfall
*                        e_tasks_started entsprechen
* <- e_tasks_missed    - Anz. Tasks, die nicht gestartet werden
*                        konnten
*--------------------------------------------------------------------*
  METHOD start_parallel_working.

    rv_work_complete = abap_false.

    lv_tasks_started = 0.
    lv_tasks_ended   = 0.
    lv_active_tasks  = 0.

    CLEAR: it_final_result.
    CLEAR: it_statistic.

    DATA(lv_tasks_missed) = 0.

* Übergebene Workitems abarbeiten
    LOOP AT i_it_input_data ASSIGNING FIELD-SYMBOL(<fs_input_data>).
* pro Workitem einen Task (asynchron) starten
      IF me->start_task( i_input_data  = <fs_input_data>
                         i_max_retries = i_max_retries ).
      ELSE.
* Fehler: kein Task für Workitem verfügbar
        lv_tasks_missed = lv_tasks_missed + 1.

        APPEND VALUE #( task_number      = -1
                        runtime          = 0
                        retries          = i_max_retries
                        input_data       = <fs_input_data>
                        output_part_size = 0
                        subrc            = -1
                        successful       = abap_false ) TO it_statistic.
      ENDIF.
    ENDLOOP.

* warten bis alle Tasks fertig sind
    WAIT FOR ASYNCHRONOUS TASKS UNTIL ( lv_active_tasks = 0 ) UP TO i_wait_time_sec SECONDS.
    IF sy-subrc = 0.
      rv_work_complete = abap_true.
    ENDIF.

* Ergebnisrückgabe
    e_it_final_result = it_final_result.
    e_it_statistic    = it_statistic.
    e_tasks_started   = lv_tasks_started.
    e_tasks_ended     = lv_tasks_ended.
    e_tasks_missed    = lv_tasks_missed.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Anzahl max. Tasks
*--------------------------------------------------------------------*
* <- rv_max_tasks - Anzahl max. Tasks
*--------------------------------------------------------------------*
  METHOD get_max_tasks.
    rv_max_tasks = lv_max_tasks.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Anzahl freie Tasks
*--------------------------------------------------------------------*
* <- rv_free_tasks - Anzahl freie Tasks
*--------------------------------------------------------------------*
  METHOD get_free_tasks.
    rv_free_tasks = lv_free_tasks.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Einen Task starten
*--------------------------------------------------------------------*
* -> i_input_data    - Eingabedaten für die Verarbeitung
* -> i_wait_time_sec - Wartezeit für Abarbeitung aller Tasks in sec
* -> i_max_retries   - max. Anz. Versuche wenn System "busy" bis
*                    - Abbruch erfolgt
* <- rv_ok           - true, wenn Task gestartet werden konnte
*--------------------------------------------------------------------*
  METHOD start_task.

    rv_ok = abap_false.

    DATA(lv_retries) = 0.

* n Neuversuche einen freien Task zu bekommen
    WHILE lv_retries < i_max_retries.
      DATA(lv_tasknumber) = me->get_free_task( ).

      IF lv_tasknumber <> -1.
        DATA(o_task) = me->get_task( lv_tasknumber ).

        IF o_task IS BOUND.

          DATA(lv_subrc) = o_task->start_async_call( i_input_data              = i_input_data
                                                     i_started_after_n_retries = lv_retries ).

          CASE lv_subrc.
            WHEN 0.
* Ok
              lv_tasks_started = lv_tasks_started + 1.
              lv_active_tasks  = lv_active_tasks + 1.
              lv_retries       = i_max_retries.

              rv_ok = abap_true.
            WHEN 1 OR 2.
* Failure
              lv_retries = i_max_retries.
            WHEN 3.
* Busy
              IF lv_tasks_started > 0.
                WAIT FOR ASYNCHRONOUS TASKS UNTIL ( lv_tasks_ended >= lv_tasks_started ) UP TO i_wait_time_sec SECONDS.
              ENDIF.

              IF sy-subrc = 0.
                lv_retries = lv_retries + 1.
              ELSE.
                lv_retries = i_max_retries.
              ENDIF.
            WHEN OTHERS.
* Sollte nicht auftreten
              lv_retries = i_max_retries.
          ENDCASE.
        ELSE.
* Task not found (sollte nicht vorkommen)
          lv_retries = lv_retries + 1.
        ENDIF.
      ELSE.
* kein freier Task verfügbar -> warten
        WAIT FOR ASYNCHRONOUS TASKS UNTIL ( lv_tasks_ended >= lv_tasks_started ) UP TO i_wait_time_sec SECONDS.
        IF sy-subrc = 0.
          lv_retries = lv_retries + 1.
        ELSE.
          lv_retries = i_max_retries.
        ENDIF.
      ENDIF.
    ENDWHILE.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Freien Task aus der Liste der verfügbaren suchen
*--------------------------------------------------------------------*
* <- rv_task_number - Tasknummer des gefundenen Tasks
*--------------------------------------------------------------------*
  METHOD get_free_task.

    rv_task_number = -1.

    LOOP AT it_tasks ASSIGNING FIELD-SYMBOL(<fs_task>).
      IF <fs_task>-o_task->is_running( ) = abap_false.
        rv_task_number = <fs_task>-task_number.
        EXIT.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Referenz auf Task anhand der Tasknummer holen
*--------------------------------------------------------------------*
* -> i_task_number - Tasknummer
* <- rv_ref_task   - Referenz auf Task
*--------------------------------------------------------------------*
  METHOD get_task.
    IF line_exists( it_tasks[ task_number = i_task_number ] ).

      ASSIGN it_tasks[ task_number = i_task_number ] TO FIELD-SYMBOL(<fs_task>).

      IF <fs_task> IS ASSIGNED.
        rv_ref_task = <fs_task>-o_task.
      ENDIF.

    ENDIF.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Eventhandler, wenn Task ferig
*--------------------------------------------------------------------*
* -> e_input_data  - Eingabedaten des Tasks
* -> e_output_data - Ausgabedaten des Tasks
* -> sender        - Referenz auf Task
*--------------------------------------------------------------------*
  METHOD on_task_completed.

* Zähler setzen
    lv_tasks_ended = lv_tasks_ended + 1.
    lv_active_tasks = lv_active_tasks - 1.

    APPEND VALUE #( task_number      = sender->get_task_number( )
                    runtime          = e_runtime
                    retries          = e_retries
                    input_data       = e_input_data
                    output_part_size = lines( e_output_data )
                    subrc            = e_subrc
                    successful       = abap_true ) TO it_statistic.

* Hier Daten an Ergebnistabelle einfach anfügen
    LOOP AT e_output_data ASSIGNING FIELD-SYMBOL(<fs_output_data>).
      APPEND VALUE #( input_data  = e_input_data
                      output_data = <fs_output_data> ) TO it_final_result.
    ENDLOOP.

  ENDMETHOD.
ENDCLASS.

[ABAP] Datensätze zwischen Tabellen mit unterschiedlichen Feldern und Datentypen kopieren

Beispiel 1 (APPEND CORRESPONDING)

* Typ für Tabelle mit kompletten Daten
TYPES: BEGIN OF ty_long,
         posnr   TYPE posnr,
         matnr   TYPE matnr,
         vbeln   TYPE vbeln,
         flag_ok TYPE boolean,
       END OF ty_long.

* Typ für Ausgabetabelle, Felder hier nur vom Datentyp string
TYPES: BEGIN OF ty_short,
         posnr TYPE string,
         matnr TYPE string,
         vbeln TYPE string,
       END OF ty_short.

DATA: it_long TYPE STANDARD TABLE OF ty_long WITH DEFAULT KEY.
DATA: it_short TYPE STANDARD TABLE OF ty_short WITH DEFAULT KEY.

* Beispieldaten
it_long = VALUE #( ( posnr = 1 matnr = '1111111111' vbeln = '3333333333' flag_ok = abap_true )
                   ( posnr = 2 matnr = '2222222222' vbeln = '4444444444' flag_ok = abap_false )
                   ( posnr = 3 matnr = '3333333333' vbeln = '5555555555' flag_ok = abap_true ) ).

* nur Datensätze kopieren, wo Flag gesetzt
LOOP AT it_long ASSIGNING FIELD-SYMBOL(<fs_l>) WHERE ( flag_ok = abap_true ).
* korrespondierende (namensgleiche) Felder des Datensatzes vom Typ ty_long an
* Tabelle mit Datensätzen vom Typ ty_short anhängen
  APPEND CORRESPONDING ty_short( <fs_l> ) TO it_short.
ENDLOOP.

* Testdaten ausgeben
LOOP AT it_short ASSIGNING FIELD-SYMBOL(<fs_s>).
  WRITE: / <fs_s>-posnr, <fs_s>-matnr, <fs_s>-vbeln.
ENDLOOP.

Beispiel 2 (CORRESPONDING, FILTER)

* Typ für Tabelle mit kompletten Daten
TYPES: BEGIN OF ty_long,
         posnr   TYPE posnr,
         matnr   TYPE matnr,
         vbeln   TYPE vbeln,
         flag_ok TYPE boolean,
       END OF ty_long.

* Typ für Ausgabetabelle, Felder hier nur vom Datentyp string
TYPES: BEGIN OF ty_short,
         posnr TYPE string,
         matnr TYPE string,
         vbeln TYPE string,
       END OF ty_short.

DATA: it_long TYPE SORTED TABLE OF ty_long WITH UNIQUE KEY posnr WITH NON-UNIQUE SORTED KEY flag COMPONENTS flag_ok.
DATA: it_short TYPE STANDARD TABLE OF ty_short WITH DEFAULT KEY.

* Beispieldaten
it_long = VALUE #( ( posnr = 1 matnr = '1111111111' vbeln = '3333333333' flag_ok = abap_true )
                   ( posnr = 2 matnr = '2222222222' vbeln = '4444444444' flag_ok = abap_false )
                   ( posnr = 3 matnr = '3333333333' vbeln = '5555555555' flag_ok = abap_true ) ).

* nur Datensätze kopieren, wo Flag gesetzt
* korrespondierende (namensgleiche) Felder des Datensatzes vom Typ ty_long in
* Tabelle mit Datensätzen vom Typ ty_short kopieren
it_short = CORRESPONDING #( FILTER #( it_long USING KEY flag WHERE flag_ok = abap_true ) ).

* Testdaten ausgeben
LOOP AT it_short ASSIGNING FIELD-SYMBOL(<fs_s>).
  WRITE: / <fs_s>-posnr, <fs_s>-matnr, <fs_s>-vbeln.
ENDLOOP.

[ABAP] MESH – Beziehungen zw. internen Tabellen abbilden (JOIN)

* https://answers.sap.com/questions/64735/what-are-meshes-really-good-for.html
* https://l3consulting.de/meshes-in-abap/
* http://zevolving.com/2015/05/abap-740-meshes-a-new-complex-type-of-structures/
* http://zevolving.com/2015/05/abap-740-mesh-path-forward-and-inverse-association/

* Demoprogramme:
*   DEMO_MESH_DELETE_NUMBERS
*   DEMO_MESH_DELETE_TABLE_NUMBERS
*   DEMO_MESH_EXPRESSIONS_FLIGHTS
*   DEMO_MESH_EXPRESSIONS_NUMBERS
*   DEMO_MESH_FORWARD_ASSOC
*   DEMO_MESH_FOR_FLIGHTS
*   DEMO_MESH_INSERT_BUILD_TABLE
*   DEMO_MESH_INSERT_NUMBERS
*   DEMO_MESH_INVERSE_ASSOC
*   DEMO_MESH_LOOP_AT_FLIGHTS
*   DEMO_MESH_LOOP_AT_NUMBERS
*   DEMO_MESH_MODIFY_NUMBERS
*   DEMO_MESH_MODIFY_TABLE_NUMBERS
*   DEMO_MESH_PACK
*   DEMO_MESH_REFLEXIVE_ASSOC_LOOP
*   DEMO_MESH_REFLEXIVE_ASSOC_SNGL
*   DEMO_MESH_REFLEXIVE_ASSOC_TREE
*   DEMO_MESH_SET_ASSOC_NUMBERS

* Orte
TYPES: BEGIN OF ty_orte,
         key_ort TYPE i,
         ort     TYPE string,
       END OF ty_orte.

* Personen
TYPES: BEGIN OF ty_personen,
         key_ort TYPE i,
         name    TYPE string,
         alter   TYPE i,
       END OF ty_personen.

TYPES: ty_it_orte TYPE SORTED TABLE OF ty_orte WITH UNIQUE KEY key_ort.
TYPES: ty_it_personen TYPE SORTED TABLE OF ty_personen WITH NON-UNIQUE KEY key_ort.

* MESH mit Assoziation
TYPES: BEGIN OF MESH ty_mesh,
         orte     TYPE ty_it_orte ASSOCIATION orte_to_personen TO personen ON key_ort = key_ort,
         personen TYPE ty_it_personen,
       END OF MESH ty_mesh.

DATA: it_mesh TYPE ty_mesh.

INITIALIZATION.
* Daten einpflegen
  it_mesh-orte = VALUE #( ( key_ort = 1 ort = 'FRA' )
                          ( key_ort = 2 ort = 'BER' )
                          ( key_ort = 3 ort = 'DRS' )
                          ( key_ort = 4 ort = 'MUN' )
                          ( key_ort = 5 ort = 'DUS' ) ).

  it_mesh-personen = VALUE #( ( key_ort = 1 name = 'Udo'      alter = 35 )
                              ( key_ort = 4 name = 'Horst'    alter = 60 )
                              ( key_ort = 3 name = 'Inge'     alter = 70 )
                              ( key_ort = 1 name = 'Elfriede' alter = 85 )
                              ( key_ort = 5 name = 'Florian'  alter = 16 ) ).

START-OF-SELECTION.

  WRITE: / 'Orte:'.
  LOOP AT it_mesh-orte ASSIGNING FIELD-SYMBOL(<o>).
    WRITE: / <o>-key_ort, <o>-ort.
  ENDLOOP.

  ULINE.

  WRITE: / 'Personen:'.
  LOOP AT it_mesh-personen ASSIGNING FIELD-SYMBOL(<p>).
    WRITE: / <p>-key_ort, <p>-name, <p>-alter.
  ENDLOOP.

  ULINE.

  TRY.
* Forward Association
      WRITE: / 'Forward Association Ort->Person ( key = 1 )'.
      DATA(lv_p) = it_mesh-orte\orte_to_personen[ it_mesh-orte[ key_ort = 1 ] ].
      WRITE: / lv_p-key_ort, lv_p-name, lv_p-alter.
    CATCH cx_root.
  ENDTRY.

  TRY.
* Inverse Assocition
      WRITE: / 'Inverse Assocition Ort->Personen->Ort( key_ort = 3 )'.
      DATA(lv_o) = it_mesh-personen\^orte_to_personen~orte[ it_mesh-personen[ key_ort = 3 ] ] .
      WRITE: / lv_o-key_ort, lv_o-ort.
    CATCH cx_root.
  ENDTRY.

[ABAP] interne Tabelle performant kopieren

komplette Tabelle durch Zuweisung

itab2 = itab1.

nur bestimmte Spalten – MOVE-CORRESPONDING

MOVE-CORRESPONDING itab1 TO itab2.

nur bestimmte Spalten – CORRESPONDING

itab2 = CORRESPONDING #( itab1 ).

manuell mit Feldsymbolen (~30% langsamer als die beiden vorherigen Varianten)

LOOP AT itab1 ASSIGNING FIELD-SYMBOL(<wa_1>).
  APPEND INITIAL LINE TO itab2 ASSIGNING FIELD-SYMBOL(<wa_2>).
  MOVE-CORRESPONDING <wa_1> TO <wa_2>.
ENDLOOP.