
ABAP Tipp - Ranges und Select-Options
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.
Inhaltsverzeichnis
In diesem Artikel schauen wir uns einmal die Grundlagen von Ranges in ABAP an, vergleichen Ranges mit Select-Options und prüfen die Notwendigkeit in der modernen Entwicklung.
Einleitung
Die Grundlage jedes klassischen Reports in SAP sind Parameter und Select-Options. Über Parameter kann ein User nur einen Wert abgrenzen, wobei er mit Select-Options eine Vielzahl von Eingrenzungen vornehmen kann. Der Vorteil ist, wenn die Select-Option einmal leer bleibt, dann werden automatisch alle Werte gezogen bzw. bleibt die Abgrenzung leer. In der modernen Entwicklung mit RAP und ABAP OO können Ranges immer noch wertvolle Dienste leisten. Deshalb wollen wir uns in diesem Artikel die Grundlagen anschauen und in die Details bei der Nutzung gehen. Dabei werden wir vor allem auf Szenarien der modernen Entwicklung eingehen.
Für die Zugriff auf die Datenbank verwenden wir die Tabelle ZBS_DCP_APPL, diese haben wir in einem RAP Artikel zur Optimierung der Suchhilfen angelegt und mit Daten befüllt. Die Tabelle und die Daten verwenden wir deshalb auch in diesem Artikel.
Aufbau
In diesem Kapitel schauen wir uns die Grundlagen und den Aufbau einer Range an. Weiterhin klären wir aktuelle Unterschiede zwischen einer Range und einer Select-Option.
Range
Eine Range ist in ABAP immer gleich aufgebaut. Es handelt sich hierbei um eine Tabelle mit vier Spalten (SIGN, OPTION, LOW und HIGH), wobei Low und High sich nach den definierten Datentypen richten. Die Datentypen und möglichen Inhalte der ersten beiden Spalten sind immer gleich.
Select-Option
Die Select-Option wird in einem Report definiert und gehört zum Selektionsbild eines Reports. Im Grunde handelt es sich hier um eine Range, da der Tabellenkörper einer Range entspricht. Der Zusatz ist hier die zusätzliche Kopfzeile, die zur Verfügung steht.
Hinweis: Die Kopfzeile ist ein obsoletes Konstrukt und sollte nicht mehr genutzt werden. In Klassen kannst du daher nur Ranges definieren und verwenden.
Standardfelder
Die ersten beiden Felder SIGN und OPTION sind standardisiert und lassen nur bestimmte Werte bzw. Konstanten zu. Mehr Informationen zu den zulässigen Werten findest du in den Domänen DDSIGN und DDOPTION als Festwerte.
Auflösung
Verwenden wir eine Range in unserem Code, dann zerlegt der Standard die Range in ein ordentliches Statement und führt einen entsprechenden Vergleich durch. Nehmen wir zum Beispiel diese Range.
DATA(filter_applications) = VALUE applications(
( sign = 'I' option = 'BT' low = 'ACC_DEF' high = 'ENGINE' )
( sign = 'E' option = 'EQ' low = 'CALC' ) ).
Führen wir nun einen SELECT auf die Daten durch und verwenden die Range zur Abgrenzung der Daten.
SELECT FROM zbs_dcp_appl
FIELDS *
WHERE application IN @filter_applications
INTO TABLE @DATA(result_with_range).
ABAP zerlegt die Range in diese Abfrage. Grundsätzlich könnten wir das manuell machen, spart uns aber viel Zeit und Performance, wenn wir die Standardfunktion verwenden. Grundsätzlich bleibt unser Code damit auch flexibel.
SELECT FROM zbs_dcp_appl
FIELDS *
WHERE application BETWEEN 'ACC_DEF' AND 'ENGINE'
AND NOT application = 'CALC'
INTO TABLE @DATA(result_without_range).
Verwendung
In diesem Abschnitt schauen wir uns einmal die Verwendung an verschiedenen Beispielen an. Bei den Beispielen gehen wir aber vor allem auf die moderne Verwendung der Range ein.
Definition
Die Range kannst du direkt oder als Typ definieren, dafür steht dir der Zusatz RANGE OF zur Verfügung. Definieren wir uns im ersten Schritt einen Typen, den wir dann zur Typisierung oder in Methodenschnittstellen verwenden können. Grundsätzlich können wir dann auch per Inline-Deklaration in unserem Code weiterarbeiten.
TYPES applications TYPE RANGE OF zbs_dcp_appl-application.
DATA(used_type) = VALUE applications( ( sign = 'I' option = 'EQ' low = 'CALC' ) ).
Definieren wir uns eine Variable lokal, können wir diese auch nur lokal verwenden und nicht wirklich an eine Schnittstelle übergeben.
DATA local TYPE RANGE OF zbs_dcp_appl-application.
local = VALUE #( ( sign = 'I' option = 'EQ' low = 'ENGINE' ) ).
Übergabe
Wie sieht es eigentlich mit der Übergabe einer Select-Option an eine Range aus? Wir wollen hier zum Beispiel die Werte aus einem Report an eine Klasse übergeben. Wie du bereits oben beim Aufbau gesehen hast, müssen wir bei der Select-Option erst den Tabellenkörper ansprechen und nicht die Kopfzeile, ansonsten wird uns der Compiler auf den fehlerhaften Typ hinweisen.
range = select_option[].
Erstellung
In diesem Beispiel erzeugen wir uns eine Range direkt durch einen SELECT und befüllen das Ergebnis in eine Range, diese können wir dann direkt verwenden für weitere Abgrenzungen. Damit sparen wir einiges an Code, für die Vorbereitung der Range. Dabei erzeugen wir über Literale feste Werte, die die Range dann definieren.
SELECT FROM zbs_dcp_appl
FIELDS 'I' AS sign,
'EQ' AS option,
application AS low,
application AS high
WHERE team = 'BASE_DEV'
INTO TABLE @DATA(generated_range).
Damit es eine gültige Range wird, müssen LOW und HIGH denselben Typen haben. Daher selektieren wir das Feld Applikation doppelt. Schauen wir uns dann die Range im Debugger an, um einen Eindruck von dieser Methode zu bekommen.
Abgrenzung
Die Range können wir dann zur Abgrenzung an verschiedenen Stellen in unserem Code verwenden. Wir können damit den üblichen SELECT durchführen. Beispiele dazu findest du in den Abschnitten davor. Oder wir verwenden eine Range zur Einschränkung der Daten in einer Schleife.
LOOP AT databases INTO DATA(database) WHERE application IN range_filters.
" ...
ENDLOOP.
Möchtest du den Inhalt eines Feldes gegen mehrere Werte abgleichen, kannst du das auch über eine Range in einem IF Statement machen.
IF database-application IN range_filters.
" ...
ENDIF.
Parameter
Wie sieht es eigentlich mit Parametern aus? Parameter über ein Selektionsbild oder Abfragen gegen einzelne Felder in einer Programmlogik sind immer etwas kompliziert in einem Select. Ist das Feld zum Beispiel nicht befüllt, dann wir im Select gegen den leeren Wert geprüft, was meist zu keinem Ergebnis mehr führt.
SELECT FROM zbs_dcp_appl
FIELDS *
WHERE application = @parameter
INTO TABLE @DATA(data_to_use)
Der Vorteil einer Range ist, wenn sie einmal leer ist, dann wird sie in der Abfrage ignoriert und alle Werte gezogen. Dazu können wir uns eine lokale Range anlegen und wenn der Parameter befüllt ist, übernehmen wir den Eintrag in die Range. Ansonsten bleibt sie leer und der Parameter wird ignoriert.
DATA optional_filter TYPE applications.
IF parameter IS NOT INITIAL.
INSERT VALUE #( sign = 'I'
option = 'EQ'
low = parameter ) INTO TABLE optional_filter.
ENDIF.
SELECT FROM zbs_dcp_appl
FIELDS *
WHERE application IN @optional_filter
INTO TABLE @DATA(data_to_use).
Standard
Wo verwendet überall der SAP Standard Ranges? Dazu ein prominentes Beispiel zur Abfrage der Filter in der Query Klasse einer Custom Entity. Über das REQUEST Objekt können wir uns die Filter als Range zurückgeben lassen, die der Anwender über die Fiori App vorgegeben hat.
TRY.
DATA(filters) = request->get_filter( )->get_as_ranges( ).
staging = filters[ name = `STAGING` ]-range[ 1 ]-low.
CATCH cx_rap_query_filter_no_range.
staging = 'TEST'.
ENDTRY.
In diesem Beispiel lassen wir uns eine Tabelle zurückgeben, in der die Felder und die dazugehörige Range als Zeilen abgelegt sind. Entsprechend suchen wir im ersten Schritt nach der Zeile mit STAGING und lesen dann die erste Zeile der Range, um den LOW Wert zu übernehmen.
Vollständiges Beispiel
Hier noch das komplette Beispiel aus dem heutigen Artikel. In der Klasse findest du in den verschiedenen Methoden die Beispiele und Verwendungen.
CLASS zcl_bs_demo_ranges_and_options DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
TYPES applications TYPE RANGE OF zbs_dcp_appl-application.
METHODS assignment
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS create_via_select
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS definition
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS technical_usage
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS delimitation
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
METHODS use_with_parameter
IMPORTING !out TYPE REF TO if_oo_adt_classrun_out
!parameter TYPE zbs_dcp_appl-application.
ENDCLASS.
CLASS zcl_bs_demo_ranges_and_options IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
technical_usage( out ).
definition( out ).
assignment( out ).
create_via_select( out ).
delimitation( out ).
use_with_parameter( out = out
parameter = 'GENERAL' ).
use_with_parameter( out = out
parameter = '' ).
ENDMETHOD.
METHOD technical_usage.
DATA(filter_applications) = VALUE applications( ( sign = 'I' option = 'BT' low = 'ACC_DEF' high = 'ENGINE' )
( sign = 'E' option = 'EQ' low = 'CALC' ) ).
SELECT FROM zbs_dcp_appl
FIELDS *
WHERE application IN @filter_applications
INTO TABLE @DATA(result_with_range).
out->write( result_with_range ).
SELECT FROM zbs_dcp_appl
FIELDS *
WHERE application BETWEEN 'ACC_DEF' AND 'ENGINE'
AND NOT application = 'CALC'
INTO TABLE @DATA(result_without_range).
out->write( result_without_range ).
ENDMETHOD.
METHOD definition.
DATA local TYPE RANGE OF zbs_dcp_appl-application.
local = VALUE #( ( sign = 'I' option = 'EQ' low = 'ENGINE' ) ).
DATA(used_type) = VALUE applications( ( sign = 'I' option = 'EQ' low = 'CALC' ) ).
ENDMETHOD.
METHOD assignment.
* range = select_option[].
ENDMETHOD.
METHOD create_via_select.
SELECT FROM zbs_dcp_appl
FIELDS 'I' AS sign,
'EQ' AS option,
application AS low,
application AS high
WHERE team = 'BASE_DEV'
INTO TABLE @DATA(generated_range).
out->write( generated_range ).
SELECT FROM zbs_dcp_appl
FIELDS *
WHERE application IN @generated_range
INTO TABLE @DATA(used_range).
out->write( used_range ).
ENDMETHOD.
METHOD delimitation.
DATA(range_filters) = VALUE applications( ( sign = 'I' option = 'EQ' low = 'CALC' ) ).
SELECT FROM zbs_dcp_appl
FIELDS *
INTO TABLE @DATA(databases).
LOOP AT databases INTO DATA(database) WHERE application IN range_filters.
out->write( database ).
ENDLOOP.
IF database-application IN range_filters.
out->write( database ).
ENDIF.
ENDMETHOD.
METHOD use_with_parameter.
DATA optional_filter TYPE applications.
IF parameter IS NOT INITIAL.
INSERT VALUE #( sign = 'I'
option = 'EQ'
low = parameter ) INTO TABLE optional_filter.
ENDIF.
* SELECT FROM zbs_dcp_appl
* FIELDS *
* WHERE application = @parameter
* INTO TABLE @DATA(data_to_use).
SELECT FROM zbs_dcp_appl
FIELDS *
WHERE application IN @optional_filter
INTO TABLE @DATA(data_to_use).
out->write( data_to_use ).
ENDMETHOD.
ENDCLASS.
Fazit
Die Range ist nach wir vor ein mächtiges Werkzeug bei der Datenbeschaffung, aber auch zur Abgrenzung innerhalb der Programmlogik. Der Unterschied zwischen Select-Option und Range sollte dir nun bekannt sein und du kannst in Zukunft Ranges effektiv in der Entwicklung einsetzen.
Quellen:
SAP Help - TYPE RANGE OF