This is a test message to test the length of the message box.
Login
ABAP Select from itab
Erstellt von Software-Heroes

ABAP - SELECT FROM @itab

Über eine interne Tabelle selektieren war früher mit vielen Zeilen Code realisiert, heute funktioniert es auch praktisch über den Select.

Werbung

In diesem Artikel wollen wir uns einmal die Erweiterung des SELECT Stamenet für lokale Tabellen anschauen und wofür wir es sinnvollerweise nutzen können. Im Nachgang wollen wir uns auch einmal die Performance beim Zugriff auf die Daten anschauen.

 

Vorbereitung

Bevor wir auf Daten zugreifen können, sollten wir uns zuerst einmal ein paar Dummy Daten generieren. Diese Daten kommen an dieser Stelle nicht aus der Datenbank, sondern wir erzeugen diese aus einer eigenen Struktur und einer eigenen kleinen Logik.

DATA(lo_random_number) = NEW zcl_demo_random( id_min = 1 id_max = 99 ).
DATA(lo_random_currency) = NEW zcl_demo_random( id_min = 1 id_max = 3 ).

DO id_number_of_entries TIMES.
  TRY.
      DATA(ls_structure) = VALUE ts_structure(
        guid = cl_system_uuid=>create_uuid_x16_static( )
        number = lo_random_number->rand( )
        text = |Item No. { sy-index }|
        currency = SWITCH #( lo_random_currency->rand( )
          WHEN 1 THEN 'EUR'
          WHEN 2 THEN 'USD'
          WHEN 3 THEN 'CHF'
        )
      ).

    CATCH cx_uuid_error.
      CONTINUE.
  ENDTRY.

  INSERT ls_structure INTO TABLE rt_result.
ENDDO.

DATA(lo_random_index) = NEW zcl_demo_random( id_min = 1 id_max = id_number_of_entries ).
DATA(ld_done) = 0.

WHILE ld_done < id_number_of_gbp.
  DATA(ld_pos) = lo_random_index->rand( ).

  IF rt_result[ ld_pos ]-currency <> 'GBP'.
    rt_result[ ld_pos ]-currency = 'GBP'.
    ld_done += 1.
  ENDIF.
ENDWHILE.

 

Es wird eine interne Tabelle mit zufälligen Daten erzeugt. In der ersten Schleife wir die Tabelle mit zufälligen Werten, Währungen und einem fortlaufenden Text befüllt. In der zweiten Schleife ändern wir eine gewisse Anzahl Zeilen und tauschen die Währung aus.

 

Standard Zugriff

Nun können wir gegen die erzeugte interne Tabelle Zugriff per SELECT durchführen, so als würden wir über eine Datenbank Tabelle lesen, dabei benötigen wir aber noch zusätzlich einen Alias für die Tabelle. Hier einige Beispiele:

" Normal Select with currency
SELECT *
  FROM @lt_standard_data AS data
  WHERE currency = 'GBP'
  INTO TABLE @DATA(lt_currency_gbp).
out->write( lt_currency_gbp ).

" Select with fields
SELECT text, number
  FROM @lt_standard_data AS data
  WHERE currency = 'GBP'
    AND number > 50
  INTO TABLE @DATA(lt_fields_gbp).
out->write( lt_fields_gbp ).

" Count
SELECT COUNT(*)
  FROM @lt_standard_data AS data
  WHERE number < 10
  INTO @DATA(ld_low_count).
out->write( ld_low_count ).

 

Im ersten Zugriff lesen wir alle Felder und schränken auf eine Währung ein, das Ergebnis sind die entsprechenden Zeilen aus der Tabelle. Im zweiten Zugriff wollen wir nur zwei Felder lesen und haben eine erweitere Abfrage gegen zwei Felder. Und wie du es vom SELECT kennst, kannst du per Inline-Deklaration auch eine neue interne Tabelle mit eigenem Zeilentyp generieren. In der dritten Abfrage wollen wir Datensätze zählen lassen, was ebenfalls funktioniert und uns die Anzahl Datensätze mit einer Nummer kleiner als 10 zurück gibt.

 

Erweiterte Zugriffe

Schauen wir uns nun einen erweiterten Fall an der etwas komplexer ist, weil wir hier noch zusätzlich mit einer Sortierung der Daten arbeiten.

SELECT *
  FROM @lt_standard_data AS data
  WHERE number > 20 AND number < 30
  ORDER BY number DESCENDING
  INTO TABLE @DATA(lt_order)
  UP TO 20 ROWS.
out->write( lines( lt_order ) ).
out->write( lt_order ).

 

Wir schränken die Daten ein, sortieren das Ergebnis und wollen davon die ersten 20 Zeilen lesen. In diesem Fall bekommen wir eine entsprechende Meldung des Compilers zu lesen.

 

Was ist also passiert? Je nach Komplexität und Zugriffsform auf die Tabelle, entscheidet das System ob die Anfrage auf dem ABAP Stack durchgeführt wird oder eine temporäre Tabelle erzeugt wird, um die Operationen darauf auszuführen. Der Compiler weißt auf diese Form hin, dass die Anfrage nicht auf dem ABAP Stack durchgeführt werden kann.

Hinweis: Mehr zu diesem Verhalten kannst du in der offiziellen Dokumentation lesen (siehe unten).

 

Performance

Wie sieht es nun mit der Performance beim Zugriff auf die Daten über die verschiedenen Formen aus? Dazu nehmen wir die Tabelle mit 2 Mio. Datensätzen und führen den SELECT auf die Standard Tabelle und die Sortierte Tabelle mit Sekundärschlüssel aus. Zusätzlich nehmen wir für diesen Fall noch das neue FILTER Statement, um an die passenden Daten zu gelangen.

SELECT *
  FROM @lt_standard_data AS data
  WHERE currency = 'GBP'
  INTO TABLE @DATA(lt_standard_gbp).

SELECT *
  FROM @lt_sorted_data AS data
  WHERE currency = 'GBP'
  INTO TABLE @DATA(lt_sorted_gbp).

DATA(lt_filtered_gbp) = FILTER #( lt_sorted_data USING KEY curr_key WHERE currency = 'GBP  ' ).

 

Entsprechend benötigen wir auch noch eine Zeitmessung, um den Vergleich durchführen zu können. Das vollständige Beispiel findest du unten. So erhalten wir nun die entsprechende Zeitmessung:

 

Am Beispiel können wir gut nachvollziehen, dass die Nutzung des SELECTs nicht ohne Performanceverlust funktioniert. Der Zugriff auf die sortierte Tabelle dauert über eine halbe Sekunde. Der Zugriff über FILTER benötigt kaum Performance. Auffällig ist auch noch, dass der Zugriff auf die Standardtabelle schneller ist, als auf die sortierten Daten. Dieses Verhalten können wir nicht genau erklären, denken aber es hängt an der Persistierung der Daten und der Verwaltung des sekundären Schlüssels.

 

Beispiel

Wie immer findest du auch am Ende des Artikels das vollständige Beispiel welches wir genutzt haben. Mehr Infos zum Zufallsgenerator findest du in einem anderen Artikel.

CLASS zcl_select_with_itab DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

    TYPES:
      BEGIN OF ts_structure,
        guid     TYPE sysuuid_x16,
        number   TYPE i,
        text     TYPE c LENGTH 40,
        currency TYPE waers,
      END OF ts_structure,

      tt_standard TYPE STANDARD TABLE OF ts_structure WITH EMPTY KEY,
      tt_sorted   TYPE SORTED TABLE OF ts_structure
        WITH UNIQUE KEY guid
        WITH NON-UNIQUE SORTED KEY curr_key COMPONENTS currency.

  PROTECTED SECTION.
  PRIVATE SECTION.
    METHODS:
      get_initial_data
        IMPORTING
                  id_number_of_entries TYPE i DEFAULT 1000
                  id_number_of_gbp     TYPE i DEFAULT 5
        RETURNING VALUE(rt_result)     TYPE tt_standard.
ENDCLASS.

CLASS zcl_tselect_with_itab IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    DATA(lt_standard_data) = get_initial_data( id_number_of_entries = 2000000 ).
    DATA(lt_sorted_data) = CORRESPONDING tt_sorted( lt_standard_data ).

    " Normal Select with currency
    SELECT *
      FROM @lt_standard_data AS data
      WHERE currency = 'GBP'
      INTO TABLE @DATA(lt_currency_gbp).
    out->write( lt_currency_gbp ).

    " Select with fields
    SELECT text, number
      FROM @lt_standard_data AS data
      WHERE currency = 'GBP'
        AND number > 50
      INTO TABLE @DATA(lt_fields_gbp).
    out->write( lt_fields_gbp ).

    " Count
    SELECT COUNT(*)
      FROM @lt_standard_data AS data
      WHERE number < 10
      INTO @DATA(ld_low_count).
    out->write( ld_low_count ).
    
    " Select with order and limitation
    SELECT *
      FROM @lt_standard_data AS data
      WHERE number > 20 AND number < 30
      ORDER BY number DESCENDING
      INTO TABLE @DATA(lt_order)
      UP TO 20 ROWS.
    out->write( lines( lt_order ) ).
    out->write( lt_order ).

    " Performance check
    DATA(lo_timer) = NEW zcl_demo_runtime( ).

    DATA(ld_start) = lo_timer->get_runtime( ).
    SELECT *
      FROM @lt_standard_data AS data
      WHERE currency = 'GBP'
      INTO TABLE @DATA(lt_standard_gbp).
    out->write( |Standard: { lo_timer->get_runtime( ) - ld_start }| ).

    ld_start = lo_timer->get_runtime( ).
    SELECT *
      FROM @lt_sorted_data AS data
      WHERE currency = 'GBP'
      INTO TABLE @DATA(lt_sorted_gbp).
    out->write( |Sorted: { lo_timer->get_runtime( ) - ld_start }| ).

    ld_start = lo_timer->get_runtime( ).
    DATA(lt_filtered_gbp) = FILTER #( lt_sorted_data USING KEY curr_key WHERE currency = 'GBP  ' ).
    out->write( |Filter: { lo_timer->get_runtime( ) - ld_start }| ).
  ENDMETHOD.


  METHOD get_initial_data.
    DATA(lo_random_number) = NEW zcl_demo_random( id_min = 1 id_max = 99 ).
    DATA(lo_random_currency) = NEW zcl_demo_random( id_min = 1 id_max = 3 ).

    DO id_number_of_entries TIMES.
      TRY.
          DATA(ls_structure) = VALUE ts_structure(
            guid = cl_system_uuid=>create_uuid_x16_static( )
            number = lo_random_number->rand( )
            text = |Item No. { sy-index }|
            currency = SWITCH #( lo_random_currency->rand( )
              WHEN 1 THEN 'EUR'
              WHEN 2 THEN 'USD'
              WHEN 3 THEN 'CHF'
            )
          ).

        CATCH cx_uuid_error.
          CONTINUE.
      ENDTRY.

      INSERT ls_structure INTO TABLE rt_result.
    ENDDO.

    DATA(lo_random_index) = NEW zcl_demo_random( id_min = 1 id_max = id_number_of_entries ).
    DATA(ld_done) = 0.

    WHILE ld_done < id_number_of_gbp.
      DATA(ld_pos) = lo_random_index->rand( ).

      IF rt_result[ ld_pos ]-currency <> 'GBP'.
        rt_result[ ld_pos ]-currency = 'GBP'.
        ld_done += 1.
      ENDIF.
    ENDWHILE.
  ENDMETHOD.
ENDCLASS.

 

Fazit

Benötigt man überhaupt noch READ TABLE wenn es nun auch SELECT gibt? DIe Antwort ist nicht leicht, vor allem wenn man auf die Performance der Anweisung schaut und bereits Funktionen existieren die es so ähnlich können. In speziellen und komplexen Fällen lohnt es sich aber über das SELECT Statment zu gehen, um so Code schlanker zu machen.

 

Quelle:
SAP Dokumentation - SELECT FROM @itab


Enthaltene Themen:
Modernes ABAPSELECTInterne Tabelle
Kommentare (0)

ABAP Deep Dive - FOR (Schleifen)

Kategorie - ABAP

Schauen wir uns einmal die FOR Schleife etwas näher an. Wie funktioniert sie? Was muss ich beachten und was kann ich damit tun?

14.04.2023

ABAP Deep Dive - Tabellenzugriff (intern)

Kategorie - ABAP

Schauen wir uns in diesem Artikel einmal den Tabellenzugriff auf interne Tabellen an und wie sie den READ TABLE ablösen.

03.02.2023

ABAP - FINAL

Kategorie - ABAP

In diesem Artikel schauen wir uns einmal das neue FINAL Sprachkonstrukt an, wie es funktioniert und was du damit anstellen kannst.

23.12.2022

ABAP Deep Dive - VALUE

Kategorie - ABAP

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.

11.11.2022

ABAP - Common Table Expression (CTE)

Kategorie - ABAP

In diesem Artikel wollen wir uns einmal den allgemeinen Tabellenausdruck WITH anschauen und wie du ihn im Alltag nutzen kannst.

28.10.2022