This is a test message to test the length of the message box.
Login
ABAP Tipp Performance
Erstellt von Software-Heroes

ABAP Tipp - Performance Datenfilterung

247

Welche Anweisung verwendest du in ABAP zur Filterung von internen Tabellen und ist diese performant? In diesem Artikel mehr dazu.

Werbung


In diesem Artikel schauen wir uns mal wieder ein Performance-Beispiel an und vergleichen verschiedene Statements und Anweisungen, um Daten aus einer Tabelle zu filtern.

 

Einleitung

In vielen Situationen kannst du bereits über die WHERE Clause eines Select-Statements die Einschränkung der Daten vornehmen, doch wie sieht es aus, wenn die Daten bereits intern als Tabelle vorliegen? In diesem Artikel werden wir uns das Verhalten und die Performance von verschiedenen Kombinationen anschauen und auf die Unterschiede eingehen.

 

Vorbereitung

Bevor wir mit den einzelnen Szenarien beginnen, bereiten wir unsere Testdaten einmal vor. Wir benötigen dazu eine Struktur für eine Tabelle mit einem Kriterium, über das wir filtern. In der Tabelle gibt es eine Identifikation, etwas Payload in Form eines Strings und ein Datum, über das wir später filtern wollen.

TYPES: BEGIN OF ts_data,
         identifier TYPE i,
         payload    TYPE string,
         sdate      TYPE d,
       END OF ts_data.
TYPES tt_data TYPE STANDARD TABLE OF ts_data WITH EMPTY KEY
      WITH NON-UNIQUE SORTED KEY by_date COMPONENTS sdate.

 

Die Daten werden zufällig generiert. Für unseren Test legen wir eine interne Tabelle mit 500.000 Einträgen an. Die Identifikation wird mit der aktuellen Zeilennummer befüllt, der Text und das Datum werden zufällig befüllt.

DATA(lo_random_date) = NEW zcl_bs_demo_random( id_min = 0
                                               id_max = 180 ).
DATA(lo_random_string) = NEW zcl_bs_demo_random( id_min = 1
                                                 id_max = 6 ).

DO c_table_entries TIMES.
  INSERT VALUE #( identifier = sy-index
                  payload    = SWITCH #( lo_random_string->rand( )
                                         WHEN 1 THEN `My text is alone`
                                         WHEN 2 THEN `Second entry of this`
                                         WHEN 3 THEN `What you need`
                                         WHEN 4 THEN `The long summer`
                                         WHEN 5 THEN `Advertising your next project`
                                         WHEN 6 THEN `A rainy day` )
                  sdate      = CONV d( cl_abap_context_info=>get_system_date( ) - lo_random_date->rand( ) ) )
         INTO TABLE mt_data.
ENDDO.

 

Zum Abschluss befüllen wir noch ein zufälliges Datum, über das wir später filtern wollen.

md_random_filter = cl_abap_context_info=>get_system_date( ) - lo_random_date->rand( ).

 

Szenarien

Im Folgenden werden die verschiedenen Szenarien und Beispiele erklärt.

 

Loop mit Data

Hierbei handelt es sich um eine einfache Schleife mit LOOP. In der WHERE Clause filtern wir die Datensätze, die wir zählen wollen und ermitteln die Summe für RD_RESULT.

LOOP AT mt_data INTO DATA(ls_data) WHERE sdate = md_random_filter.
  rd_result += 1.
ENDLOOP.

 

Loop mit Assigning

Das zweite Beispiel ist recht nah am ersten mit dem einfachen Loop, hier verwenden wir ein Feldsymbol bei der Zuweisung, um uns den Unterschied zur Inline-Deklaration anzuschauen.

LOOP AT mt_data ASSIGNING FIELD-SYMBOL(<ls_data>) WHERE sdate = md_random_filter.
  rd_result += 1.
ENDLOOP.

 

Loop mit Schlüssel

Im dritten Beispiel verwenden wir einen Loop, dabei verwenden wir aber explizit den sekundären Schlüssel. Das sollte die Performance beim Zugriff auf die Tabelle erhöhen und schneller als der einfache Loop sein.

LOOP AT mt_data ASSIGNING FIELD-SYMBOL(<ls_data>) USING KEY by_date WHERE sdate = md_random_filter.
  rd_result += 1.
ENDLOOP.

 

Filter

In diesem Szenario verwenden wir das FILTER Statement, um die Datensätze einzuschränken. Filter setzt immer einen Schlüssel voraus, damit wir auch effektiv damit arbeiten können. Für das Ergebnis ermitteln wir dann per LINES das Ergebnis.

rd_result = lines( FILTER #( mt_data USING KEY by_date WHERE sdate = md_random_filter ) ).

 

Reduce

Das Reduzierungs-Statement REDUCE ist ebenfalls ein neuer Befehl, damit können wir die Daten verarbeiten und die Anzahl der gefilterten Datensätze ableiten. Bei der Verwendung nutzen wir ebenfalls den sekundären Schlüssel der Tabelle.

rd_result = REDUCE #(
  INIT ld_count TYPE i
  FOR <ls_data> IN mt_data USING KEY by_date WHERE ( sdate = md_random_filter )
  NEXT ld_count += 1 ).

 

FOR

Als letztes verwenden wir die FOR Schleife, um die Datensätze zu filtern und per LINES das Ergebnis zu ermitteln. Im Grunde werden die gleichen Schritte wie beim FILTER angewandt.

rd_result = lines( VALUE tt_data( FOR <ls_data> IN mt_data USING KEY by_date WHERE ( sdate = md_random_filter )
                                  ( CORRESPONDING #( <ls_data> ) ) ) ).

 

Ergebnis

Das Ergebnis des Durchlaufs für 500.000 Zeilen in der internen Tabelle:

 

Der einfache Loop, egal ob mit der Inline-Deklaration DATA oder FIELD-SYMBOLS ist in etwa ähnlich schnell, hier sollte es nicht allzu große Abstände geben. Etwas überraschend ist die Performance mit Loop und die Angabe des sekundären Schlüssels, hier hätten wir eigentlich erwartet, dass die Logik schneller als der einfache Loop wäre. Nach etwas Recherche in der SAP Dokumentation scheint der erste und zweite Zugriff automatisch über den Optimizer zu laufen, da wir den sekundären Schlüssel angegeben haben. Das erklärt allerdings nicht die Performance für die dritte Variante.

Filter und Reduce haben das Performance-Rennen gewonnen, die FOR Schleife mit der WHERE Bedingung ist etwas langsamer, aber auch noch sehr schnell.

 

Vollständiges Beispiel

Das vollständige Beispiel und den Code findest du hier. Mehr Informationen zur Random-Klasse und zur Runtime-Klasse findest du in verschiedenen Artikeln.

CLASS zcl_bs_demo_filtering DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

    TYPES: BEGIN OF ts_data,
             identifier TYPE i,
             payload    TYPE string,
             sdate      TYPE d,
           END OF ts_data.
    TYPES tt_data TYPE STANDARD TABLE OF ts_data WITH EMPTY KEY
          WITH NON-UNIQUE SORTED KEY by_date COMPONENTS sdate.

  PRIVATE SECTION.
    CONSTANTS c_table_entries TYPE i VALUE 500000.

    DATA mt_data          TYPE tt_data.
    DATA md_random_filter TYPE d.

    METHODS prepare_random_data.

    METHODS run_basic_loop_data
      RETURNING VALUE(rd_result) TYPE i.

    METHODS run_basic_loop_assigning
      RETURNING VALUE(rd_result) TYPE i.

    METHODS run_loop_with_key
      RETURNING VALUE(rd_result) TYPE i.

    METHODS run_filter_and_lines
      RETURNING VALUE(rd_result) TYPE i.

    METHODS run_reduce
      RETURNING VALUE(rd_result) TYPE i.

    METHODS run_for_lines
      RETURNING VALUE(rd_result) TYPE i.
ENDCLASS.


CLASS zcl_bs_demo_filtering IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    prepare_random_data( ).

    DATA(lo_run) = NEW zcl_bs_demo_runtime( ).
    DATA(ld_count) = run_basic_loop_data( ).
    out->write( |Basic Loop (DATA) - { ld_count }     : { lo_run->get_diff( ) }| ).

    lo_run = NEW zcl_bs_demo_runtime( ).
    ld_count = run_basic_loop_assigning( ).
    out->write( |Basic Loop (ASSIGNING) - { ld_count }: { lo_run->get_diff( ) }| ).

    lo_run = NEW zcl_bs_demo_runtime( ).
    ld_count = run_loop_with_key( ).
    out->write( |Loop with key - { ld_count }         : { lo_run->get_diff( ) }| ).

    lo_run = NEW zcl_bs_demo_runtime( ).
    ld_count = run_filter_and_lines( ).
    out->write( |Filter and Lines - { ld_count }      : { lo_run->get_diff( ) }| ).

    lo_run = NEW zcl_bs_demo_runtime( ).
    ld_count = run_reduce( ).
    out->write( |Reduce - { ld_count }                : { lo_run->get_diff( ) }| ).

    lo_run = NEW zcl_bs_demo_runtime( ).
    ld_count = run_for_lines( ).
    out->write( |FOR and Lines - { ld_count }         : { lo_run->get_diff( ) }| ).
  ENDMETHOD.


  METHOD prepare_random_data.
    DATA(lo_random_date) = NEW zcl_bs_demo_random( id_min = 0
                                                   id_max = 180 ).
    DATA(lo_random_string) = NEW zcl_bs_demo_random( id_min = 1
                                                     id_max = 6 ).

    DO c_table_entries TIMES.
      INSERT VALUE #( identifier = sy-index
                      payload    = SWITCH #( lo_random_string->rand( )
                                             WHEN 1 THEN `My text is alone`
                                             WHEN 2 THEN `Second entry of this`
                                             WHEN 3 THEN `What you need`
                                             WHEN 4 THEN `The long summer`
                                             WHEN 5 THEN `Advertising your next project`
                                             WHEN 6 THEN `A rainy day` )
                      sdate      = CONV d( cl_abap_context_info=>get_system_date( ) - lo_random_date->rand( ) ) )
             INTO TABLE mt_data.
    ENDDO.

    md_random_filter = cl_abap_context_info=>get_system_date( ) - lo_random_date->rand( ).
  ENDMETHOD.


  METHOD run_basic_loop_data.
    LOOP AT mt_data INTO DATA(ls_data) WHERE sdate = md_random_filter.
      rd_result += 1.
    ENDLOOP.
  ENDMETHOD.


  METHOD run_basic_loop_assigning.
    LOOP AT mt_data ASSIGNING FIELD-SYMBOL(<ls_data>) WHERE sdate = md_random_filter.
      rd_result += 1.
    ENDLOOP.
  ENDMETHOD.


  METHOD run_loop_with_key.
    LOOP AT mt_data ASSIGNING FIELD-SYMBOL(<ls_data>) USING KEY by_date WHERE sdate = md_random_filter.
      rd_result += 1.
    ENDLOOP.
  ENDMETHOD.


  METHOD run_filter_and_lines.
    rd_result = lines( FILTER #( mt_data USING KEY by_date WHERE sdate = md_random_filter ) ).
  ENDMETHOD.


  METHOD run_reduce.
    rd_result = REDUCE #(
      INIT ld_count TYPE i
      FOR <ls_data> IN mt_data USING KEY by_date WHERE ( sdate = md_random_filter )
      NEXT ld_count += 1 ).
  ENDMETHOD.


  METHOD run_for_lines.
    rd_result = lines( VALUE tt_data( FOR <ls_data> IN mt_data USING KEY by_date WHERE ( sdate = md_random_filter )
                                      ( CORRESPONDING #( <ls_data> ) ) ) ).
  ENDMETHOD.
ENDCLASS.

 

Fazit

Wenn es um die Filterung von Datensätzen geht, solltest du das FILTER Statement verwenden, allerdings benötigt deine Tabelle dann auch einen sekundären sortierten Schlüssel, um von der Geschwindigkeit zu profitieren. Insgesamt bist du gut beraten, die neuen Statements zu verwenden.


Enthaltene Themen:
TippPerformanceDatenfilterung
Kommentare (0)



Und weiter ...

Bist du zufrieden mit dem Inhalt des Artikels? Wir posten jeden Dienstag und Freitag neuen Content im Bereich ABAP und unregelmäßig in allen anderen Bereichen. Schaue bei unseren Tools und Apps vorbei, diese stellen wir kostenlos zur Verfügung.


ABAP in Praxis - Fiori Daten fehlerhaft

Kategorie - ABAP

In diesem kleinen Praxisbeispiel schauen wir uns einen Fehlerfall in Fiori an. Hier werden die Daten im UI falsch angezeigt, obwohl alles sonst richtig zu sein scheint. Die Spur führt uns durch den RAP Stack in eine andere Richtung.

10.10.2025

ABAP Tipp - Handling von Funktionsbausteinen

Kategorie - ABAP

Wie gehst du eigentlich mit Funktionsbausteinen und der Fehlerbehandlung innerhalb von ABAP um? In diesem kleinen Tipp schauen wir uns die Behandlung auch im Rahmen von RFC an.

26.08.2025

ABAP Tipp - Generische Datentypen

Kategorie - ABAP

Was unterscheidet eigentlich CLIKE von CSEQUENCE? Generische Typen können manchmal etwas undurchsichtig sein und als ABAP Entwickler wählen wir vielleicht den Typen zu generisch.

12.08.2025

Recycling-Heroes (Erklärt)

Kategorie - ABAP

Was haben die Recycling-Heroes mit moderner ABAP Entwicklung und ABAP Cloud zu tun? In diesem Artikel geben wir Einblicke in die Idee.

15.07.2025

ABAP Tipp - Ranges und Select-Options

Kategorie - ABAP

Ranges und Select-Options in ABAP sind sehr ähnlich und doch gibt es feine Unterschiede bei der Nutzung im ABAP OO Kontext. Hier schauen wir uns die moderne Verwendung an.

09.05.2025