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

144

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 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 - String Verarbeitung

Kategorie - ABAP

In diesem praktischen Beispiel schauen wir uns die String Verarbeitung zur Ermittlung der CDS Namen in CamelCase an und wie du das mit ABAP umsetzen kannst.

15.10.2024

ABAP in der Praxis - Test Driven Development

Kategorie - ABAP

Wie funktioniert eigentlich TDD in der Praxis und gibt es einfache Beispiele zum Lernen in ABAP? In dieser Übung gehen wir auf den praktischen Teil ein.

24.09.2024

ABAP in der Praxis - Datenmenge zusammenführen

Kategorie - ABAP

Wir führen wir zwei unterschiedliche Datenmengen in ABAP zusammen, vor allem im Hinblick auf das Moderne ABAP? Eine praktische Aufgabe zum Thema.

17.09.2024

ABAP in der Praxis - Modern ABAP

Kategorie - ABAP

In dieser kleinen Aufgabe schauen wir uns bestehenden klassischen ABAP Quellcode an und versuchen diesen nach Modern ABAP zu optimieren.

27.08.2024

ABAP in der Praxis - Typkonvertierung

Kategorie - ABAP

Wie würdest du diese Typkonvertierung in ABAP durchführen? Ein Beispiel aus der Praxis und ein Lösungsvorschlag.

16.07.2024