[ABAP] OpenSQL: Unterschied SELECT, SELECT SINGLE, SELECT DISTINCT

SELECT

* Selektiert alle Einträge für 'DE' und 'US', mit Mehrfachnennungen
SELECT cityto
  INTO TABLE @DATA(it_dest)
  FROM spfli
  WHERE countryfr = 'DE'
     OR countryfr = 'US'.

*CITYTO:
*SAN FRANCISCO
*NEW YORK
*FRANKFURT
*SAN FRANCISCO
*NEW YORK
*TOKYO
*NEW YORK
*FRANKFURT
*NEW YORK
*BERLIN
*FRANKFURT
*SINGAPORE
*SINGAPORE
*SAN FRANCISCO
*FRANKFURT
*FRANKFURT
*NEW YORK

cl_demo_output=>display_data( it_dest ).

SELECT DISTINCT

* Selektiert alle Einträge für 'DE' und 'US', ohne Mehrfachnennungen
SELECT DISTINCT cityto
  INTO TABLE @DATA(it_dest)
  FROM spfli
  WHERE countryfr = 'DE'
     OR countryfr = 'US'.

*CITYTO:
*TOKYO
*BERLIN
*NEW YORK
*SINGAPORE
*FRANKFURT
*SAN FRANCISCO
cl_demo_output=>display_data( it_dest ).

SELECT SINGLE

* Selektiert den ersten Datensatz für 'DE' und 'US'
SELECT SINGLE cityto
  INTO @DATA(lv_dest)
  FROM spfli
  WHERE countryfr = 'DE'
     OR countryfr = 'US'.

*CITYTO:
*SAN FRANCISCO
cl_demo_output=>display_data( lv_dest ).

[ABAP] OpenSQL: Zusammenführen von Tabelleninhalten mit SELECT UNION

Variante 1 (UNION == UNION DISTINCT – einfaches Auftreten der Datensätze)

DATA: lv_devclass TYPE string VALUE 'ZABAP2XLSX'.

SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'PROG' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'CLAS' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'FUGR' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'DEVC' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'DOMA' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'DTEL' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'INTF' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'MSAG' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'TABL' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'TTYP' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'WDYA' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'XSLT' AND devclass = @lv_devclass
UNION DISTINCT
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'WDYN' AND devclass = @lv_devclass
 ORDER BY obj_name
 INTO TABLE @DATA(it_prog_range).

DATA(lv_lines) = lines( it_prog_range ).
cl_demo_output=>write_data( lv_lines ).
cl_demo_output=>write_data( it_prog_range ).
cl_demo_output=>display( ).

Variante 2 (UNION ALL – mehrfach auftretende Datensätze)

DATA: lv_devclass TYPE string VALUE 'ZABAP2XLSX'.

SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'PROG' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'CLAS' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'FUGR' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'DEVC' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'DOMA' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'DTEL' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'INTF' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'MSAG' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'TABL' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'TTYP' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'WDYA' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'XSLT' AND devclass = @lv_devclass
UNION ALL
SELECT obj_name
  FROM tadir
  WHERE pgmid = 'R3TR' AND object = 'WDYN' AND devclass = @lv_devclass
 ORDER BY obj_name
 INTO TABLE @DATA(it_prog_range).

DATA(lv_lines) = lines( it_prog_range ).
cl_demo_output=>write_data( lv_lines ).
cl_demo_output=>write_data( it_prog_range ).
cl_demo_output=>display( ).

Links

[ABAP] OpenSQL: Datensätze aus einer internen Tabelle zu einer DB-Tabelle hinzufügen

  • COMMIT WORK wird am Programmende automatisch ausgeführt
  • Aktionen können mit ROLLBACK rückgängig gemacht werden

Variante 1 (einzelner Datensatz)

* Datensatz
DATA(lv_struct) = VALUE zxyz( col1 = 'Value1' col1 = 'Value2' ).

INSERT INTO zxyz VALUES @lv_struct.
IF sy-subrc = 0.
  WRITE : / 'Hinzufügen erfolgreich.'.
ELSE.
  WRITE : / 'Datensatz konnte nicht hinzugefügt werden.'.
ENDIF.

Variante 2 (mehrere Datensätze aus einer itab)

* itab mit Datensätzen
DATA(itab) = VALUE zxyz_tab(
                             ( col1 = 'Value1' col1 = 'Value2' )
                             ( col1 = 'Value3' col1 = 'Value4' )
                           ).
* Wenn der Zusatz ACCEPTING DUPLICATE KEYS angegeben ist, werden alle Zeilen eingefügt, für die dies möglich ist.
* Die restlichen Zeilen werden verworfen und sy-subrc wird auf 4 gesetzt.
INSERT zxyz FROM TABLE itab ACCEPTING DUPLICATE KEYS.

IF sy-subrc = 0.
  WRITE : / 'Hinzufügen erfolgreich.'.
ELSE.
  WRITE : / 'Nicht alle Zeilen der Tabelle itab wurden hinzugefügt.'.
ENDIF.

* sy-dbcnt = Anzahl der eingefügten Zeilen
WRITE: / 'Hinzugefügte Datensätze:', sy-dbcnt.

[ABAP] OpenSQL: Vergleich von SELECT-Abfragen über RANGES, FOR ALL ENTRIES, JOINS

Allgemein

In den folgenden Beispielen werden drei der gängigen Datenbankzugriffe über ein SELECT nach Laufzeit sowie Vor- und Nachteilen verglichen.

Variante 1 (RANGE)

* Laufzeit: 0,086142s

* Vorteile:
*   - Aufteilen und Manipulieren von verknüpften DB-Abfragen
*   - Abfrage in einem Block -> gut für kleine Abfragen mit wenigen RANGE-Einträgen
*   - Verwendung z.B. bei Nutzung von SELECT-OPTIONS
* Nachteile:
*   - Laufzeitverhalten bei großen Abfragen
*   - keine Abhängigkeiten von zwei oder mehr Feldern in einem RANGE abbildbar (Verknüpfung in der WHERE-Clause erfolgt nur einzeln über AND/OR)
*   - RANGES werden beim SELECT in ein Statement mit vielen ORs umgewandelt
*     -> abhängig vom DB-System kommt es früher oder später zu DUMPS, wenn der RANGE zu viele Einträge beinhaltet (Speichergröße der RANGE-Table > 64kB)
DATA: lv_maktx TYPE maktx VALUE '<empty>'.

DATA(o_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(usec_start) = o_timer->get_runtime( ).

* SELECT 1: Materialnummern
SELECT m~matnr, @lv_maktx AS maktx
  INTO TABLE @DATA(it_mara)
  FROM mara AS m
  UP TO 1000 ROWS.

DATA: rg_matnr TYPE RANGE OF matnr.

* MATNR in RANGE kopieren, darauf achten, dass die Suchfelder UNIQUE sind
rg_matnr = VALUE #( FOR <m> IN it_mara
                    (
                      sign   = 'I'
                      option = 'EQ'
                      low    = <m>-matnr
                      high   = ''
                    ) ).

* SELECT 2: Kurztexte
SELECT t~matnr, t~maktx
  INTO TABLE @DATA(it_makt)
  FROM makt AS t
  WHERE t~matnr IN @rg_matnr
    AND t~spras = @sy-langu.

* Zuweisung MATNR, MAKTX
LOOP AT it_mara ASSIGNING FIELD-SYMBOL(<t>).
  IF line_exists( it_makt[ matnr = <t>-matnr ] ).
    <t>-maktx = it_makt[ matnr = <t>-matnr ]-maktx.
  ENDIF.
ENDLOOP.

DATA(usec_end) = o_timer->get_runtime( ).
DATA(usec) = CONV decfloat16( usec_end - usec_start ).
DATA(sec) = usec / 1000000.

WRITE: / 'Laufzeit: ', sec, 's'.

cl_demo_output=>display( it_mara  ).

Variante 2 (FOR ALL ENTRIES)

* Laufzeit: 0,073252s

* Vorteile:
*   - Aufteilen und Manipulieren von verknüpften DB-Abfragen
*   - mehrere FOR ALL ENTRIES können pro Statement verwendet werden
*   - Verwendung z.B. bei Verarbeitung von vielen Datensätzen
* Nachteile:
*   - Laufzeitverhalten bei großen Abfragen
*   - wenn die FOR ALL ENTRIES Liste leer ist, wir die WHERE-Clause ignoriert, was zu unerwünschten Nebeneffekten führen kann
*   - FOR ALL ENTRIES werden beim SELECT in viele einzelne Statements umgewandelt
*   - man sollte darauf achten, dass die Einträge in der FOR ALL ENTRIES-Tabelle nur einmal auftreten -> Performance
*   - Speicherverbrauch durch die vielen Abfragen
* Anmerkungen:
*   - doppelte Einträge in der FOR ALL ENTRIES Tabelle werden entfernt (SELECT DISTINCT)
*   - wichtige Parameter für die Performance von FOR ALL ENTRIES sind:
*     rsdb/max_blocking_factor
*     rsdb/min_blocking_factor
*     rsdb/max_in_blocking_factor
*     rsdb/min_in_blocking_factor
*     rsdb/prefer_in_itab_opt
*     rsdb/prefer_fix_blocking
DATA: lv_maktx TYPE maktx VALUE '<empty>'.

DATA(o_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(usec_start) = o_timer->get_runtime( ).

* SELECT 1: Materialnummern
* darauf achten, dass die Einträge in der FOR ALL ENTRIES Tabelle UNIQUE sind
SELECT m~matnr, @lv_maktx AS maktx
  INTO TABLE @DATA(it_mara)
  FROM mara AS m
  UP TO 1000 ROWS.

* Prüfung auf IS INITIAL ist wichtig, da sonst die WHERE-Clause im SELECT ignoriert würde
IF NOT it_mara IS INITIAL.

* SELECT 2: Kurztexte
  SELECT t~matnr, t~maktx
    INTO TABLE @DATA(it_makt)
    FROM makt AS t
    FOR ALL ENTRIES IN @it_mara
    WHERE t~matnr = @it_mara-matnr
      AND t~spras = @sy-langu.

* Zuweisung MATNR, MAKTX
  LOOP AT it_mara ASSIGNING FIELD-SYMBOL(<m>).
    IF line_exists( it_makt[ matnr = <m>-matnr ] ).
      <m>-maktx = it_makt[ matnr = <m>-matnr ]-maktx.
    ENDIF.
  ENDLOOP.
ENDIF.

DATA(usec_end) = o_timer->get_runtime( ).
DATA(usec) = CONV decfloat16( usec_end - usec_start ).
DATA(sec) = usec / 1000000.

WRITE: / 'Laufzeit: ', sec, 's'.

cl_demo_output=>display( it_mara  ).

Variante 3 (JOIN)

* Laufzeit: 0,012196s

* Vorteile:
*   - wesentlich schneller als RANGE und FOR ALL ENTRIES
*   - eine komplexe DB-Anfrage (generisch)
*   - Verwendung bei Verarbeitung von vielen Datensätzen verteilt über viele Tabellen
* Nachteile:
*   - komplexe Statements
*   - schlecht zu Debuggen
DATA(o_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(usec_start) = o_timer->get_runtime( ).

* SELECT: Materialnummern, Kurztexte
SELECT m~matnr, t~maktx
  INTO TABLE @DATA(it_mara)
  FROM mara AS m
  RIGHT OUTER JOIN makt AS t ON ( m~matnr = t~matnr )
  UP TO 1000 ROWS
  WHERE t~spras = @sy-langu.

DATA(usec_end) = o_timer->get_runtime( ).
DATA(usec) = CONV decfloat16( usec_end - usec_start ).
DATA(sec) = usec / 1000000.

WRITE: / 'Laufzeit: ', sec, 's'.

cl_demo_output=>display( it_mara  ).

Links

[ABAP] OpenSQL: Subselect / Subqueries verwenden

* Subqueries können nicht für Pool- oder Clustertabellen verwendet werden
* ORDER BY kann nicht in einer Subquery verwendet werden
* bei der Verwendung von Subqueries wird das SAP buffering umgangen

* Beispiel 1 (Städte)
SELECT city,
       latitude,
       longitude
INTO TABLE @DATA(it_cities)
FROM sgeocity
WHERE city IN ( SELECT cityfrom FROM spfli WHERE carrid = 'LH' ).

cl_demo_output=>display( it_cities ).

* Beispiel 2 (Buchungen)
SELECT c~id,
       c~name,
       c~city,
       b~cancelled
  INTO TABLE @DATA(it_cust)
  UP TO 100 ROWS
  FROM sbook as b
  INNER JOIN scustom as c ON c~id = b~customid
  WHERE customid NOT IN ( SELECT customid FROM sbook WHERE cancelled EQ @abap_true ).

cl_demo_output=>display( it_cust ).