SELECT in ABAP

In der Programmiersprache ABAP ist der SELECT-Befehl eine der am meisten benutzten ABAP-Befehlen überhaupt. Mit diesem Befehl kann man Daten aus einer Datenbanktabelle holen, diese eventuell aufbereiten und letztlich dem Benutzer zur Anzeige bringen.

Das Hasso-Plattner-Institut hat in Untersuchungen festgestellt, dass nur 20 % der Interaktionen Einfüge-, Änderungs- oder Löschbefehle auf den Datenbanken sind. In 80 % der Fälle werden Daten aus einer Datenbank ausgelesen und dem Benutzer angezeigt. Demzufolge ist der SELECT-Befehle auch eine der am häufig ausgeführten Befehle zur Laufzeit.

In diesem Artikel wird der Befehl SELECT umfangreich dargestellt und näher erläutert. Mit Beispielprogrammen kann man einfach und schnell sein Programm erweitern.

SELECT in ABAP – Definition

Der ABAP-Befehl SELECT dient dem Auslesen von Daten in einer Datenbank. Das Grundgerüst der Abfrage besteht aus den vier Schlüsselwörter SELECT, FROM, INTO und WHERE.

Übersicht der Struktur einer SELECT-Abfrage unter ABAP.

Hierbei dienen „spalten“, „tabelle“, „ziel_felder“ und „bedingung“ als Platzhalter und müssen ersetzt werden. Die vollständige Syntax der SELECT-Abfrage lautet:

SELECT spalten
FROM tabelle
INTO|APPENDING ziel_felder
[[FOR ALL ENTRIES IN lt_tabelle] WHERE bedingung]
[GROUP BY spalten] [HAVING group_bedingung]
[ORDER BY sort_spalte].
...
[ENDSELECT.]

Im Folgenden werden zwei Varianten für den SELECT-Befehl aufgezeigt. Hierbei wird in der Variante 1 direkt das Ergebnis in eine interne Tabelle geschrieben. In Variante 2 wird das Ergebnis in eine Struktur geschrieben, da man ENDSELECT verwendet. Bei Variante 2 muss man darauf achten, dass eine Schleife durchlaufen wird. Fügt man die Ergebnismenge nicht einer Tabelle hinzu, erhält man nur die letzte gefundene Zeile.

*----------------------------------------------------------------------
* Datendeklaration
*----------------------------------------------------------------------
DATA: lt_mara1 TYPE TABLE OF mara, "Tabelle von MARA
      lt_mara2 TYPE TABLE OF mara, "Tabelle von MARA
      ls_mara2 TYPE mara. "Struktur von MARA

*----------------------------------------------------------------------
* Auslesen der Daten direkt in einer Tabelle - Variante 1
*----------------------------------------------------------------------
SELECT * INTO CORRESPONDING FIELDS OF TABLE lt_mara1
  FROM mara
  WHERE matnr = '1234567890'.


*----------------------------------------------------------------------
* Auslesen der Daten in eine Struktur - Variante 2
*----------------------------------------------------------------------
SELECT * INTO CORRESPONDING FIELDS OF ls_mara2
  FROM mara
  WHERE matnr = '1234567890'.

  " Struktur in Tabelle anhängen
  APPEND ls_mara2 TO lt_mara2.

ENDSELECT.

Die SELECT-Anweisung liest aus einer oder mehreren Datenbanktabellen, Views oder CDS-Entitäten Daten in ein oder mehrere Zielfelder. Dabei gibt man die gewünschten Metadaten als Spaltennamen der Tabelle und die Bedingung für die genaue Selektion mit an. Das Ergebnis kann ein einziges Feld, mehrere Felder, eine Struktur oder eine Tabelle sein. Das hängt davon ab wie man die Abfrage verwendet.

Bei der Verwendung von SELECT werden die Systemfelder sy-subrc und sy-dbcnt gesetzt. Diese können nach der SELECT-Abfrage ausgelesen werden. Das eignet sich vor allem für das Fehlerhandling und Logging.

sy-subrcBeschreibung
0Eintrag in der Tabelle gefunden.
Hinweis: Das Ergebnis kann leer sein, wenn es in der Tabelle leer ist.
4Keinen Eintrag in der Tabelle gefunden / Ergebnismenge ist leer.
8Es wird der Zusatz FOR UPDATE verwendet, ohne den vollständigen Primärschlüssel in der WHERE-Bedingung zu verwenden.

Im Folgenden werden die wichtigsten Zusätze zum SELECT-Befehl näher erläutert.

SELECT mit Alias

Ein Alias ist ein alternativer Name. Man kann einen SELECT auch mit einem Alias verwenden. Dabei wird eine Spalte umbenannt. Das wird durch das Schlüsselwort AS ermöglicht. Vor allem bei langen Tabellennamen oder bei JOIN machen Alias durchaus Sinn.

DATA:
  lt_cityto TYPE TABLE OF s_to_city.

PARAMETERS: p_c_fr TYPE spfli-countryfr.

*----------------------------------------------------------------------
* SELECT mit Alias
*----------------------------------------------------------------------
SELECT DISTINCT s~cityto
  INTO TABLE @lt_cityto
  FROM spfli AS s
  WHERE s~countryfr = @p_c_fr.

SELECT – DISTINCT

Mit dem Zusatz DISTINCT entfernt man die Duplikate. Dadurch sind keine Mehrfachnennungen möglich und man erhält nur unterschiedliche Werte.

DATA:
  lt_cityto TYPE TABLE OF s_to_city.

PARAMETERS: p_c_fr TYPE spfli-countryfr.

*----------------------------------------------------------------------
* SELECT DISTINCT
*----------------------------------------------------------------------
SELECT DISTINCT cityto
  INTO TABLE @lt_cityto
  FROM spfli
  WHERE countryfr = @p_c_fr.

SELECT – SINGLE

Ein SELECT SINGLE ist eine Art der Tabellenabfrage. Hierbei wird mit dem Zusatz SINGLE der SELECT-Befehl in ABAP erweitert. Die Ergebnismenge wird dadurch einzeilig und liefert genau einen Treffer bzw. eine Zeile. Und dabei wird der erste gefundene Treffer zurückgegeben.

DATA:
  lv_cityto TYPE s_to_city.

PARAMETERS: p_c_fr TYPE spfli-countryfr.

*----------------------------------------------------------------------
* SELECT SINGLE
*----------------------------------------------------------------------
SELECT SINGLE cityto
  INTO @lv_cityto
  FROM spfli
  WHERE countryfr = @p_c_fr.

SELECT – GROUP BY

Der Zusatz GROUP BY sorgt für eine Verdichtung von mehreren Zeilen anhand gleicher Werte der angegebenen Spalte. Dabei muss man alle Spalten gruppieren bzw. verdichten, die in der SELECT-Klausel ohne eine Aggregatfunktion verwendet werden.

TYPES: BEGIN OF ty_spfli,
         cityfr       TYPE land1,
         cityto       TYPE s_to_city,
         sum_distance TYPE s_distance,
       END OF ty_spfli.

DATA:
  lt_spfli TYPE TABLE OF ty_spfli.

*----------------------------------------------------------------------
* SELECT GROUP BY
*----------------------------------------------------------------------
SELECT cityfrom, cityto, SUM( distance ) AS sum_distance
  INTO TABLE @lt_spfli
  FROM spfli
  GROUP BY cityfrom, cityto.

SELECT – HAVING

Der Zusatz HAVING schränkt bereits verdichtete Zeilen ein. Man kann demzufolge den HAVING-Zusatz nur im Zusammenhang mit GROUP BY verwenden.

Bei der Bedingung kann man folgendes verwenden:

  • Variablen
  • Ausdrücke
  • Gruppierte Spalten in GROUP BY
  • SQL-Ausdrücke
  • Aggregatfunktionen
TYPES: BEGIN OF ty_spfli,
         cityfr       TYPE land1,
         cityto       TYPE s_to_city,
         sum_distance TYPE s_distance,
       END OF ty_spfli.

DATA:
  lt_spfli TYPE TABLE OF ty_spfli.

*----------------------------------------------------------------------
* SELECT GROUP BY & HAVING
*----------------------------------------------------------------------
SELECT cityfrom, cityto, SUM( distance ) AS sum_distance
  INTO TABLE @lt_spfli
  FROM spfli
  GROUP BY cityfrom, cityto
  HAVING cityfrom = 'TOKYO'.

SELECT – ORDER BY

ORDER BY ist vor allem unter SAP S/4HANA ein wichtiger Zusatz, denn die Daten werden bei der HANA-Datenbank nicht sortiert zurückgegeben. Bei einem Custom Code Check wird das Fehlen von einem ORDER BY-Zusatz oft als Warnung ausgegeben.

Durch ORDER BY wird eine mehrzeilige Ergebnismenge nach einer bestimmten Spalte auf- oder absteigend sortiert. Man kann dabei auch die Sortierung basierend auf mehrere Spalten anwenden.

Der Zusatz ASCENDING sorgt für eine aufsteigende Sortierung. DESCENDING für eine absteigende Sortierung. Wenn man nichts angibt, wird standardmäßig ASCENDING angewendet.

Zusätzlich kann man nach dem Primärschlüssel sortieren, indem man PRIMARY KEY anwendet. Dafür müssen aber alle Spalten des Primärschlüssels im SELECT-Teil angegeben werden oder über SELECT *. Im FROM-Teil darf keine VIEW oder ein JOIN-Ausdruck auftreten.

Als Alternative zur Sortierung gibt es den SORT-Befehl in ABAP. Damit kann man interne Tabellen sortieren. Hierbei muss man jedoch beachten, dass die Sortierung vom Anwendungsserver durchgeführt wird. ORDER BY hingegen wird auf dem Datenbankserver ausgeführt.

Beim SINGLE-Zusatz kann man logischerweise kein ORDER BY anwenden, da man sowieso nur eine Zeile erhält.

TYPES: BEGIN OF ty_spfli,
         cityfr   TYPE land1,
         cityto   TYPE s_to_city,
         distance TYPE s_distance,
       END OF ty_spfli.

DATA:
  lt_spfli TYPE TABLE OF ty_spfli.

*----------------------------------------------------------------------
* SELECT ORDER BY
*----------------------------------------------------------------------
SELECT cityfrom, cityto, distance
  INTO TABLE @lt_spfli
  FROM spfli
  ORDER BY distance DESCENDING.

SELECT – OPEN CURSOR

Bei den bisherig gezeigten Zusätzen wird das Lesen und Zuweisen der Daten in ein internes Feld, eine interne Struktur oder eine interne Tabelle in einem gemeinsamen Schritt ausgeführt. Diese Kopplung kann man explizit trennen, indem man einen sogenannten Cursor verwendet.

Dabei verwendet man folgende Reihenfolge:

  1. Cursor öffnen – OPEN CURSOR
  2. Daten selektieren – SELECT
  3. Ergebnismenge einem Datenobjekt zuweisen – FETCH
  4. Cursor schließen – CLOSE CURSOR

Als Erstes muss man einen Cursor öffnen und gewünschte Abfrage festlegen. Hierfür verwendet man folgende Syntax:

OPEN CURSOR [WITH HOLD] cur FOR
SELECT spalten
FROM tabelle
[[FOR ALL ENTRIES IN lt_tabelle] WHERE bedingung]
[GROUP BY spalten] [HAVING group_bedingung]
[ORDER BY sort_spalte].

Bei der Abfrage kann man die herkömmliche Syntax außer die Zusätze SINGLE, INTO und APPENDING verwenden. Der Cursor muss ein ABAP-Datenobjekt des vordefinierten Typs cursor sein.

Um die Daten auch im Zugriff zu haben, muss man mit einem FETCH-Befehl die Ergebnismenge ein Feld, eine Struktur oder eine Tabelle zuweisen.

FETCH NEXT CURSOR cur [INTO|APPENDING] ziel_felder.

Die Variable cur steht weiterhin für den zuvor geöffneten Cursor, auf den die Abfrage erstellt wurde. Der Cursor wird folglich um so viele Zeilen nach vorne gesetzt, wie viele Zeilen aus der Ergebnismenge ausgelesen wurden.

Abschließend schließt man den Cursor. Das ist technisch nicht unbedingt notwendig, wird aber sehr empfohlen, da die Anzahl an geöffneten Cursor begrenzt ist.

CLOSE CURSOR cur.

Im Folgenden sieht man ein Beispielprogramm, das die Daten aus den Tabellen SPFLI und SFLIGHT mit einem CURSOR über eine Fremdschlüsselbeziehung selektieren und anzeigen.

DATA: wa_spfli        TYPE spfli,
      wa_sflight      TYPE sflight,
      wa_sflight_back TYPE sflight.

DATA: c1 TYPE cursor,
      c2 TYPE cursor.

OPEN CURSOR @c1 FOR
  SELECT *
    FROM spfli
    ORDER BY PRIMARY KEY.

OPEN CURSOR @c2 FOR
  SELECT *
         FROM sflight
         ORDER BY PRIMARY KEY.

DO.
  FETCH NEXT CURSOR @c1 INTO @wa_spfli.
  IF sy-subrc NE 0.
    EXIT.
  ENDIF.
  WRITE: / wa_spfli-carrid, wa_spfli-connid.
  DO.
    IF NOT wa_sflight_back IS INITIAL.
      wa_sflight = wa_sflight_back.
      CLEAR wa_sflight_back.
    ELSE.
      FETCH NEXT CURSOR @c2 INTO @wa_sflight.
      IF  sy-subrc <> 0.
        EXIT.
      ELSEIF wa_sflight-carrid <> wa_spfli-carrid
          OR wa_sflight-connid <> wa_spfli-connid.
        wa_sflight_back = wa_sflight.
        EXIT.
      ENDIF.
    ENDIF.
    WRITE: / wa_sflight-carrid, wa_sflight-connid, wa_sflight-fldate.
  ENDDO.
ENDDO.

CLOSE CURSOR: @c1, @c2.

Das Programm ist in Anlehnung an das Beispielprogramm von der SAP SE programmiert.

SELECT – JOIN

Mithilfe des Begriffes JOIN kann man mit einer SELECT-Abfrage die Ergebnismenge von zwei Tabellen verbinden. Das ist deshalb wichtig, da die Tabellen einer Datenbank normalisiert sind. Das bedeutet, dass die Daten strukturiert und frei von Redundanzen/Anomalien sind. Deshalb sind viele Tabellen mit einer Fremdschlüsselbeziehung verbunden.

Es gibt insgesamt folgende Möglichkeiten, einen JOIN aufzubauen:

  • INNER
  • LEFT, RIGHT (OUTER)
  • CROSS

Inner JOIN

Bei einem INNER JOIN werden die Ergebnismengen der linken Abfrage mit der Ergebnismenge der rechten Abfrage so kombiniert, dass die Bedingung gemeinsam erfüllt ist. Einen INNER JOIN kann man also mit einer Schnittmenge vergleichen.

TYPES: BEGIN OF ty_usr,
         bname      TYPE xubname,
         persnumber TYPE ad_persnum,
         smtp_addr  TYPE ad_smtpadr,
       END OF ty_usr.

DATA: ls_usr TYPE ty_usr.

PARAMETERS: p_name TYPE string DEFAULT 'ANDREASG'.

*----------------------------------------------------------------------
* SELECT - INNER JOIN
*----------------------------------------------------------------------
SELECT SINGLE u~bname,
       u~persnumber,
       a~smtp_addr
  INTO @ls_usr
  FROM usr21 AS u
  INNER JOIN adr6 AS a ON a~persnumber = u~persnumber AND a~addrnumber = u~addrnumber
  WHERE u~bname = @p_name.

Outer Join

Bei einem OUTER JOIN werden für jede selektierte Zeile der linken Seite (LEFT OUTER JOIN) bzw. der rechten Seite (RIGHT OUTER JOIN) mindestens eine Zeile in der Ergebnismenge erzeugt. Wenn es auf der anderen Seite keine passende Zeile gibt, wird in der Ergebnismenge dieser Wert in der Zeile mit einem Null-Wert aufgefüllt. So stellt man sicher, dass man alle Zeilen auf einer Seite mit Sicherheit in der Ergebnismenge hat.

TYPES: BEGIN OF ty_usr,
         bname      TYPE xubname,
         persnumber TYPE ad_persnum,
         smtp_addr  TYPE ad_smtpadr,
       END OF ty_usr.

DATA: ls_usr TYPE ty_usr.

PARAMETERS: p_name TYPE string DEFAULT 'ANDREASG'.

*----------------------------------------------------------------------
* SELECT - LEFT OUTER JOIN
*----------------------------------------------------------------------
SELECT SINGLE u~bname,
       u~persnumber,
       a~smtp_addr
  FROM usr21 AS u
  LEFT OUTER JOIN adr6 AS a ON a~persnumber = u~persnumber AND a~addrnumber = u~addrnumber
  WHERE u~bname = @p_name
  INTO @ls_usr.

Cross JOIN

Bei einem CROSS JOIN werden alle möglichen Zeilen der linken Tabelle mit den Zeilen der rechten Tabelle verknüpft. Die Ergebnismenge enthält dabei alle möglichen Kombinationen. Die Anzahl der Zeilen der Ergebnismenge ist das Produkt aus den Zeilen der einzelnen Tabellen.

Bei einem CROSS JOIN enthält als Beispiel die Ergebnismenge 12 Zeilen, wenn Tabelle 1 4 Zeilen und Tabelle 2 3 Zeilen enthält.

Ein CROSS JOIN wirkt wie ein INNER oder OUTER JOIN, dessen ON-Bedingung immer wahr ist. Ein Cross JOIN sollte immer mit Vorsicht verwendet werden, da es keine ON-Bedingung gibt.

Hinweis: Ab dem Release 7.51 wird bei der Verwendung eines CROSS JOIN die Syntaxprüfung im strikten Modus ausgeführt. Es empfiehlt sich generell, einen INNER oder OUTER JOIN zu verwenden.

*----------------------------------------------------------------------
* SELECT - CROSS JOIN
*----------------------------------------------------------------------
SELECT
  s~name,
  d~departmentname
  FROM zstudents AS s
  CROSS JOIN zdepartments AS d
  INTO TABLE @DATA(lt_data).

Aggregatfunktionen

Man hat bei der Abfrage von Daten die Möglichkeit, sogenannte Aggregatfunktionen zu verwenden. Dadurch ist es möglich, Berechnungen direkt auf der Ebene der Datenbank auszuführen.

Falls man eine Aggregatfunktion verwendet, muss man alle nicht in den Aggregatfunktionen verwendeten Spalten in den GROUP BY-Teil gruppieren. Wenn man jedoch nur Aggregatfunktionen in dem SELECT-Teil benutzt, muss man keine GROUP BY-Klausel anwenden.

Falls man in der selektierten Datenbanktabelle in einzelnen Felder NULL-Werte hat, werden diese bei den Funktionen nicht berücksichtigt. NULL ist auf Ebene der Datenbank ein leerer Wert und nicht die Zahl 0.

Man sollte bei Aggregatfunktionen stets auf den Datentyp des Zielfeldes achten. Wenn man den falschen Datentyp wählt, kann es vorkommen, dass man bei einer Zahl eine Nachkommastelle verliert oder die Variable „überläuft“.

Unter ABAP SQL kann man folgende Aggregatfunktionen verwenden:

SYNTAXBeschreibung
AVG( [DISTINCT] spalte )Durchschnittswert der Werte der angegebenen Spalte
COUNT( DISTINCT spalte )Anzahl an unterschiedlichen Werten der angegebenen Spalte
COUNT(*)Anzahl der Zeilen
MAX( [DISTINCT] spalte )Maximalwert in der angegebenen Spalte
MIN( [DISTINCT] spalte ) Minimalwert in der angegebenen Spalte
SUM( [DISTINCT] spalte )Summe der Werte der angegebenen Spalte
DATA: lv_avg       TYPE p DECIMALS 2,
      lv_count     TYPE i,
      lv_count_all TYPE i,
      lv_max       TYPE p DECIMALS 2,
      lv_min       TYPE p DECIMALS 2,
      lv_sum       TYPE p DECIMALS 2.

*----------------------------------------------------------------------
* SELECT - Aggregatfunktionen
*----------------------------------------------------------------------
SELECT  AVG( rmwwr ),
        COUNT( DISTINCT belnr ),
        COUNT(*),
        MAX( rmwwr ),
        MIN( rmwwr ),
        SUM( rmwwr )
  FROM rbkp INTO (@lv_avg, @lv_count, @lv_count_all, @lv_max, @lv_min, @lv_sum).

SELECT ab ABAP 7.40

Ab dem Release von 7.40 von ABAP sind neue Syntaxelemente dazugekommen. Im Folgenden werden die wichtigsten Syntaxerweiterungen für SELECT ab ABAP 7.40 vorgestellt.

Du kannst einfach und schnell die installierte ABAP-Version ermitteln, indem Du folgendermaßen vorgehst:

  1. Menüleiste: System > Status…
  2. Button „Details“ unter Produktversion im Bereich „SAP-Systemdaten“
  3. Release-Version der Komponente SAP_Basis überprüfen
Release-Version von ABAP in SAP ERP ermitteln

Bei den bisher Codebeispielen wurde bereits die neue Syntax verwendet. Insgesamt stehen folgende Neuerungen zur Verfügung:

  • Inline-Deklaration
  • Kommagetrennte SELECT-Felder
  • Substring
  • Cast Substring in einem INNER JOIN
  • Alle Felder einer Tabelle in einem INNER JOIN
  • CASE
  • coalesce-Funktion
  • UNION
  • Verkettungsoperator
  • Festwerte

Inline-Deklaration

Mit der Inline-Deklaration kann man interne Felder oder Tabellen direkt bei der Zuweisung deklarieren. Sie müssen vorher nicht explizit deklariert, typisiert und damit bekannt sein. Zur Laufzeit des Programms ergeben sich diese Informationen.

Die Inline-Deklaration wird mit @DATA ermöglicht. Zwischen den darauffolgenden Klammern gibt man den Variablennamen an.

*----------------------------------------------------------------------
* SELECT - Abfrage alle Informationen einer Bestellung - Tabelle
*----------------------------------------------------------------------
SELECT *
       FROM ekko
       WHERE ebeln = '1234567890'
       INTO TABLE @DATA(lt_ekko).

*----------------------------------------------------------------------
* SELECT SINGLE - Abfrage alle Informationen einer Bestellung - Struktur
*----------------------------------------------------------------------
SELECT SINGLE *
       FROM ekko
       WHERE ebeln = '1234567890'
       INTO @DATA(ls_ekko).

*----------------------------------------------------------------------
* SELECT SINGLE - Abfrage des Erstellers einer Bestellung - Variable
*----------------------------------------------------------------------
SELECT SINGLE ernam
      FROM ekko
      WHERE ebeln = '1234567890'
      INTO @DATA(lv_ernam).

Kommagetrennte SELECT-Felder

Unter ABAP 7.4 werden die gewünschten selektierten Spalten mit einem Komma getrennt.

*----------------------------------------------------------------------
* SELECT - Abfrage alle Informationen einer Bestellung - Tabelle
*----------------------------------------------------------------------
SELECT ebeln, bukrs, ernam
       FROM ekko
       WHERE ebeln = '1234567890'
       INTO TABLE @DATA(lt_ekko).

Substring

Direkt bei den ausgewählten Spalten kann man den Inhalt mit der SUBSTRING-Funktion bearbeiten und in die Ergebnismenge speichern. Das hat den großen Vorteil, dass man nicht im Nachhinein über eine LOOP-Schleife die Daten in der Spalte verändern muss. Die SUBSTRING-Funktion hat dabei drei Parameter: Wert, Anfang, Ende.

Wichtig: Die erste Ziffer der Zeichenkette beginnt mit 1 und nicht typischerweise mit 0. Die Angabe von 0 als Anfang führt zu einem Syntaxfehler.

*----------------------------------------------------------------------
* SELECT SUBSTRING - Referenzschlüssel BKPF
* 10 Zeichen Belegnummer
* 4 Zeichen Buchungskreis
* 4 Zeichen Geschäftsjahr
*----------------------------------------------------------------------
SELECT belnr,
       bukrs,
       gjahr,
       substring( awkey, 1, 10 ) AS ref_belnr,
       substring( awkey, 11, 4 ) AS ref_bukrs,
       substring( awkey, 15, 4 ) AS ref_gjahr
  FROM bkpf
  INTO TABLE @DATA(lt_bkpf).

Cast Substring in einem INNER JOIN

Es gibt Tabellenfelder, die speichern den gleichen Inhalt, haben aber eine unterschiedliche Typisierung. Beispiel: KONV-KPOSN (Länge 6) und EKPO-EBELP (Länge 5).

Ein INNER JOIN führt deshalb zu keiner Schnittmenge und damit keinem gleichen Ergebnis. Man kann deshalb mit der CAST-Funktion Feldbedingungen in einen gewünschten Datentyp umwandeln.

Beim Casten muss man natürlich immer darauf aufpassen, dass Informationen verloren gehen können. Deshalb muss man mit Vorsicht casten.

Bei CDS-Views kann man die CAST-Funktion nicht anwenden.

*----------------------------------------------------------------------
* SELECT - CAST INNER JOIN
*----------------------------------------------------------------------
SELECT ekko~ebeln, ekpo~ebelp, konv~kbetr
FROM ekko
  INNER JOIN ekpo
  ON ekko~ebeln = ekpo~ebeln
INNER JOIN konv
  ON konv~knumv = ekko~knumv
  AND CAST( substring( konv~kposn, 2, 5 ) AS NUMC( 5 ) ) = ekpo~ebelp
  INTO TABLE @DATA(lt_eko_ekpo_konv).

Alle Felder einer Tabelle in einem INNER JOIN

Man kann nun bei einem INNER JOIN alle Felder einer Tabelle mit * ansprechen. Dabei darf man das nicht bei zwei oder mehr Tabellen anwenden. Das hat den Vorteil, direkt alle Felder einer Tabelle einfach und schnell anzusprechen.

Das Ergebnis der *-Abfrage ist eine Struktur in der Ergebnismenge.

Ob das performant und Sinn ergibt, muss man individuell entscheiden. Es wird immer empfohlen, nur die notwendigen Spalten und damit Werte zu lesen.

*----------------------------------------------------------------------
* SELECT - *-Abfrage bei INNER JOIN
*----------------------------------------------------------------------
SELECT ekko~*, ekpo~ebelp
FROM ekko
  INNER JOIN ekpo
  ON ekko~ebeln = ekpo~ebeln
INTO TABLE @DATA(lt_ekko_ekpo).

CASE

Man kann mit der neuen ABAP-Syntax direkt in der Abfrage einen CASE verwenden, um in Abhängigkeit von bestimmten Informationen Werte zu setzen. Beispielsweise ist es sinnvoll, bei bestimmten Betragsgrenzen Icons zu setzen.

*----------------------------------------------------------------------
* SELECT CASE
*----------------------------------------------------------------------
SELECT
  belnr, bukrs, gjahr,
  CASE WHEN rmwwr < 1000 THEN '@S_TL_G@'   " Grüne Ampel
       WHEN rmwwr < 10000 THEN '@S_TL_Y@'  " Gelbe Ampel
       WHEN rmwwr >= 10000 THEN '@S_TL_R@' " Rote Ampel
       ELSE ' '
  END AS icon
  FROM rbkp
  INTO TABLE @DATA(lt_rbkp).

coalesce-Funktion

Bei einem LEFT OUTER JOIN kann es vorkommen, dass einzelne Spalten leer sind, da in der verknüpften Tabelle kein passender Eintrag gefunden werden kann. Mit der coalesce-Funktion kann man NULL-Werte ersetzen.

*----------------------------------------------------------------------
* SELECT - LEFT OUTER JOIN COALESCE
*----------------------------------------------------------------------
SELECT u~bname,
       u~persnumber,
       COALESCE( a~smtp_addr, '---' )
  FROM usr21 AS u
  LEFT OUTER JOIN adr6 AS a ON a~persnumber = u~persnumber AND a~addrnumber = u~addrnumber
  WHERE u~bname IN @s_name
    INTO table @lt_usr.

UNION

Mit UNION werden die Ergebnismenge von zwei SELECT-Anweisungen kombiniert und vereinigt. Die Spalten der Ergebnismenge werden durch die linke SELECT-Abfrage definiert. Dabei sind die einzelnen Abfragen getrennt zu betrachten. Sie haben ihren eigenen SELECT-, FROM-, WHERE-, GROUP BY- und HAVING-Teil.

Beim UNION-Zusatz kann man auch ORDER BY und INTO anwenden. Diese gelten jedoch auf die gesamte Ergebnismenge.

Zusätzlich kann man UNION ALL oder UNION DISTINCT verwenden. Bei UNION ALL werden alle Zeilen selektiert und in die Ergebnismenge geschrieben. Bei UNION DISTINCT werden die Duplikate in der Ergebnismenge entfernt.

TYPES: BEGIN OF ty_spfli,
         cityfr   TYPE land1,
         cityto   TYPE s_to_city,
         distance TYPE s_distance,
       END OF ty_spfli.

DATA:
  lt_spfli TYPE TABLE OF ty_spfli.

*----------------------------------------------------------------------
* SELECT UNION ALL
*----------------------------------------------------------------------
SELECT cityfrom, cityto, distance
  FROM spfli
  WHERE cityfrom = 'TOKYO'
 UNION ALL
SELECT cityfrom, cityto, distance
  FROM spfli
  WHERE cityfrom = 'ROME'
  INTO TABLE @lt_spfli.

*----------------------------------------------------------------------
* SELECT UNION DISTINCT
*----------------------------------------------------------------------
SELECT cityfrom, cityto, distance
  FROM spfli
  WHERE cityfrom = 'TOKYO'
 UNION DISTINCT
SELECT cityfrom, cityto, distance
  FROM spfli
  WHERE cityfrom = 'ROME'
  INTO TABLE @lt_spfli.

Verkettungsoperator

Man kann mit dem Verkettungsoperator && Zeichenketten einer Zeile in ein Zielfeld verknüpfen. Man muss dabei die Zielspalte mit dem Schlüsselwort AS explizit benennen.

*----------------------------------------------------------------------
* SELECT - Verkettungsoperator
*----------------------------------------------------------------------
SELECT name_first && ' ' && name_last AS name
  FROM user_addr
  INTO TABLE @DATA(lt_user_addr).

Festwerte

Man kann bei der Abfrage eine Spalte in der Ergebnismenge mit einem festen Wert zu füllen.

*----------------------------------------------------------------------
* SELECT - Festwerte
*----------------------------------------------------------------------
SELECT @sy-uname AS runner,
       'ERP UP - SELECT' AS text,
      belnr, gjahr, bukrs
  FROM bkpf
  INTO TABLE @DATA(lt_bkpf).

Tabellenzugriff aus ABAP-Programmen

Beim Thema Tabellenzugriff muss man wissen, dass es verschiedene Möglichkeiten in ABAP gibt. Hierbei muss man generell zwischen Native SQL und ABAP SQL/OpenSQL unterscheiden.

Native SQL

Mit Native SQL ist es möglich, spezifische Befehle des zugrundeliegenden Datenbanksystems zu verwenden. Folglich hängt der Sprachumfang von der verwendeten Datenbank ab.

Um die datenbankspezifischen Befehle verwenden zu können, verwendet man folgende Syntax:

EXEC SQL.
" Datenbankspezifische Befehle
ENDEXEC.

Demzufolge kann man beispielsweise in seinem ABAP-Programm eine Native SQL-Abfrage verwenden.

*----------------------------------------------------------------------
* Datendeklaration
*----------------------------------------------------------------------
DATA: lv_carrname TYPE s_carrname,
      lv_currcode TYPE s_currcode,
      lv_url      TYPE s_carrurl.
PARAMETERS p_carrid TYPE s_carr_id.

*----------------------------------------------------------------------
* NATIVE SQL - SELECT
*----------------------------------------------------------------------
EXEC SQL.
  SELECT carrname, currcode, url
    FROM scarr INTO :lv_carrname, :lv_currcode, :lv_url
    WHERE carrid = :p_carrid
ENDEXEC.

*----------------------------------------------------------------------
* Ausgabe
*----------------------------------------------------------------------
WRITE: / lv_carrname, lv_currcode, lv_url.

Man sollte stets beachten, dass die verwendeten Native SQL Befehle in den ABAP-Programmen auf eine einzige Datenbank ausgelegt sind. Tauscht man die zugrundeliegende Datenbank aus, so muss man die Programmierungen anpassen, wen die spezifischen Befehle nicht mehr unterstützt werden.

ABAP SQL/OpenSQL

Mit ABAP SQL bzw. Open SQL bedient man sich dem Sprachgebrauch der Programmiersprache ABAP. Es handelt sich dabei um datenbankspezifische ABAP-Befehle, die unabhängig von der zugrundeliegenden Datenbank verwendet werden.

Im Vergleich zu Native SQL hat ABAP SQL bzw. Open SQL den großen Vorteil, dass die Programmstellen bei einem Wechsel der Datenbank nicht angepasst werden müssen. Als Nachteil kann man leider die datenbankspezifischen Befehle nicht benutzen.

ABAP SQL ist dabei der neue Name für die ABAP Plattform auf SAP S/4HANA. Den Fachbegriff OpenSQL hat man unter den NetWeaver-Systemen verwendet.

Die wichtigsten Befehle unter ABAP SQL bzw. OpenSQL sind folgende:

  • SELECT
  • INSERT
  • UPDATE
  • MODIFY
  • DELETE
  • OPEN CURSOR, FETCH, CLOSE CURSOR
*----------------------------------------------------------------------
* Datendeklaration
*----------------------------------------------------------------------
DATA: lv_carrname TYPE s_carrname,
      lv_currcode TYPE s_currcode,
      lv_url      TYPE s_carrurl.
PARAMETERS p_carrid TYPE s_carr_id.

*----------------------------------------------------------------------
* ABAP SQL bzw. OpenSQL - SELECT
*----------------------------------------------------------------------
SELECT carrname, currcode, url
  FROM scarr INTO (@lv_carrname, @lv_currcode, @lv_url)
  WHERE carrid = @p_carrid.
ENDSELECT.

*----------------------------------------------------------------------
* Ausgabe
*----------------------------------------------------------------------
WRITE: / lv_carrname, lv_currcode, lv_url.

Die SELECT-Abfrage im oberen Beispiel verwendet die neue OpenSQL-Syntax mit dem Escaping der Hostvariablen (@-Zeichen). Man kann auch die alte Syntax verwenden. Das Beispiel hierfür sieht folgendermaßen aus:

*----------------------------------------------------------------------
* ABAP SQL bzw. OpenSQL - SELECT mit alter Syntax
*----------------------------------------------------------------------
SELECT carrname currcode url
  FROM scarr INTO (lv_carrname, lv_currcode, lv_url)
  WHERE carrid = p_carrid.
ENDSELECT.

SELECT – Tipps und Tricks

Vor allem beim SELECT-Befehl ist es sehr wichtig, auf einen guten ABAP-Programmierstil zu achten. Denn dadurch werden Daten von der Datenbank gelesen. Wenn man unnötige Daten liest, leider die Performance darunter.

Schnellerer Zugriff bei internen Tabellen

Bei internen Tabellen sollte man Feldsymbole verwenden anstatt Strukturen. Das hat folgende Vorteile:

  • höhere Performance, da keine unnötigen Daten kopiert werden
  • geringere Fehleranfälligkeit, da die interne Tabelle mit geändert wird, wenn sich das Feldsymbol ändert
LOOP AT lt_test ASSIGNING <ls_test>.
    <ls_test>-value = 'TEST'.
ENDLOOP.

Bei READ TABLE muss man prüfen, ob das Feldsymbol zugewiesen ist, da ansonsten eine Excpetion geworfen wird, die das Programm abstürzen lässt.

READ TABLE lt_test WITH KEY id = '1' ASSIGNING <ls_test>.
IF sy-subrc = 0.
" Zugriff auf das Feldsymbol <ls_test>
ENDIF.

Zugriff auf Datenbank

Das Verhalten und die Performance des Systems hängt davon ab wie viel Last auf der Datenbank entsteht, wie viele Daten zwischen der Datenbank und dem Applikationsserver übertragen werden müssen und letztlich auch wie viel Last auf dem Application Server selbst entsteht. Für diese Gründe ist es ratsam, die Anfragen auf die Datenbank so selten wie möglich ausführen zu lassen und auch nur die Daten abzufragen, die man wirklich benötigt.

Nur Daten lesen, die man wirklich benötigt

Beispiel für schlechten Code:

SELECT * FROM SPFLI INTO ls_spfli.
WRITE: / ls_spfli-carrid.
ENDSELECT.

Beispiel für besseren Code:

SELECT carrid FROM SPFLI INTO ls_spfli-carrid.
WRITE: / ls_spfli-carrid.
ENDSELECT. 

Vollständigen Schlüssel angeben

Beispiel für schlechten Code:

SELECT * FROM sflight INTO ls_sflight WHERE connid = '1234'.

Beispiel für besseren Code:

SELECT * FROM sflight INTO ls_sflight WHERE carrid = 'LH' AND connid = '1234'.

Wiederholte, einzelne Zugriffe vermeiden

Beispiel für schlechten Code:

SELECT SINGLE * FROM SPFLI INTO ls_spfli WHERE CARRID='LH' AND
CONNID='1234'.
SELECT SINGLE * FROM SPFLI INTO ls_spfli WHERE CARRID='LH' AND
CONNID='1235'.
SELECT SINGLE * FROM SPFLI INTO ls_spfli WHERE CARRID='LH' AND
CONNID='1236'.

Beispiel für besseren Code:

SELECT * FROM SPFLI INTO ... WHERE CARRID='LH'.

ENDSELECT.

Joins verwenden anstatt geschachtelter SELECTs

Beispiel für schlechten Code:

SELECT feld1 FROM tabelle1 INTO wa.
    SELECT feld2 FROM tabelle2 INTO wa2 WHERE feld2 = wa-feld1
    ENDSELECT.
ENDSELECT.

Beispiel für besseren Code:

SELECT feld1 feld2 FROM tabelle1 INNER JOIN tabelle2 ON tabelle1-feld1 = tabelle2-feld2.
ENDSELECT.

WHERE-Bedingungen, BETWEEN und LIKE

Bei WHERE-Bedingungen sollte man niemals negative Formulierungen wie z.B. “<>” oder “NOT” verwenden. Der Datenbank-Optimizer kann hier nicht mehr eingreifen und wird deshalb immer einen Full Table Scan durchführen. Das sorgt für Performanceeinbußen.

BETWEEN sollte ebenfalls nicht mehr verwendet werden. Stattdessen lieber “IN” benutzen.

Versuche stets bei Zugriffen auf die Datenbank den LIKE-Operator zu vermeiden. Das ist einerseits keine eindeutige Suche und kann die Performance verschlechtern.

In der ABAP-Schlüsselwortdokumentation erfährt man weitere interessante Hinweise und Erklärungen zu SELECT.

Hier ist die ABAP-Kurzreferenz zu SELECT.

Über den Autor

Andreas Geiger

Mein Name ist Andreas Geiger und ich bin ein erfahrener Senior SAP Berater. Mit mehr als 10 Jahren Berufserfahrung habe ich mehrere SAP-Projekte erfolgreich abgeschlossen und umfangreiche Kenntnisse in verschiedenen Bereichen wie SAP FI, SAP MM und ABAP erworben. Nun möchte ich mein Wissen mit Dir teilen, um Dir einen Mehrwert zu bieten und Dich bei Deiner täglichen Arbeit mit dem SAP-System zu unterstützen.

Mehr zu ERP UP

ERP UP unterstützen

Wenn Du mit ERP UP zufrieden bist, kannst Du mich gerne unterstützen. Dabei gibt es unzählige Möglichkeiten, wie Du mich einfach und schnell unterstützen kannst. Wie Du genau ERP UP unterstützen kannst, erfährst Du hier. Vielen Dank.

Schreibe einen Kommentar