[ABAP] Pivot-Darstellung von Feldwerten eines CDS-Views / SELECT-Statements (CASE, SUM, STRING_AGG, substring_regexpr, RegEx, PCRE, UNION, COLLECT)

Variante 1 (CDS-View mit CASE, SUM, GROUP BY)

* Daten werden hier mit CDS-View R_ProdValuationLedgerAccountTP (Product Valuation Ledger Account - TP) gelesen
* Suche der Periodischen Verrechnungspreise im Material Ledger über
* CDS-View r_prodvaluationledgeraccounttp (Product Valuation Ledger Account - TP).
* Das CDS-View liefert zeilenweise zu jedem Währungstyp einen Preis, die jeweils leeren Zeilen für die Preise
* (price_10, price_30, price_31) werden mit dem Wert '0.0' vorbelegt.
* Durch die Summenbildung pro Gruppe werden die Datensätze zu einer Zeile mit den zug. Preisen in Spalten zusammengefasst.
* Die Typkonvertierung abap.curr <-> abap.dec ist notwendig, da die Funktion sum( ) nur mit abap.dec arbeitet.
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Testview für PIVOT'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.usageType:{
    serviceQuality: #X,
    sizeCategory: #S,
    dataClass: #MIXED
}
define view entity Z_CDS_PIVOT
  as select from R_ProdValuationLedgerAccountTP
{
  key Product,
  key ValuationArea,
  key ValuationType,
  @EndUserText.label: 'Ledger'
  key Ledger,
      Currency,
  // Summe der Spalte MovingAveragePrice für Währungstyp 10
  @EndUserText.label: 'Preis WT 10'
  @Semantics.amount.currencyCode: 'Currency'
      cast(sum(case when CurrencyRole = '10' then cast(MovingAveragePrice as abap.dec( 16, 2 )) else 0.0 end) as abap.curr( 16, 2 )) as price_10,
  // Summe der Spalte MovingAveragePrice für Währungstyp 30
  @EndUserText.label: 'Preis WT 30'
  @Semantics.amount.currencyCode: 'Currency'
      cast(sum(case when CurrencyRole = '30' then cast(MovingAveragePrice as abap.dec( 16, 2 )) else 0.0 end) as abap.curr( 16, 2 )) as price_30,
  // Summe der Spalte MovingAveragePrice für Währungstyp 31
  @EndUserText.label: 'Preis WT 31'
  @Semantics.amount.currencyCode: 'Currency'
      cast(sum(case when CurrencyRole = '31' then cast(MovingAveragePrice as abap.dec( 16, 2 )) else 0.0 end) as abap.curr( 16, 2 )) as price_31
}
// Gruppierung, damit die Summenbildung oben pro Gruppe (Produkt / Währungstyp) erfolgt
group by Product,
         ValuationArea,
         ValuationType,
         Ledger,
         Currency,
         MovingAveragePrice

Variante 2 (SELECT, SUM, CASE, GROUP BY)

* Im Beispiel werden durch Anweisungen in einem SELECT-Statement mehrere Zeilen einer Rückgabemenge zu einer Zeile zusammengefasst (gruppiert).
* Kern der Funktion zur Umwandlung der Zeilenwerte in Spaltenwerte sind die Funktionen:
*  SUM( )
*  CASE
*  GROUP BY

PARAMETERS: p_matnr TYPE r_prodvaluationledgeraccounttp-product DEFAULT '1122334455'.
PARAMETERS: p_bwkey TYPE r_prodvaluationledgeraccounttp-valuationarea DEFAULT '0001'.
PARAMETERS: p_bwtar TYPE r_prodvaluationledgeraccounttp-valuationtype DEFAULT ''.
PARAMETERS: p_ledger TYPE r_prodvaluationledgeraccounttp-ledger DEFAULT '0L'.

START-OF-SELECTION.
* Suche der Periodischen Verrechnungspreise im Material Ledger über
* CDS-View r_prodvaluationledgeraccounttp (Product Valuation Ledger Account - TP).
* Das SELECT liefert zeilenweise zu jedem Währungstyp einen Preis, die jeweils leeren Zeilen für die Preise
* (price_10, price_30, price_31) werden mit dem Wert dec`0.00` vorbelegt.
* Durch die Summenbildung pro Gruppe werden die Datensätze zu einer Zeile mit den zug. Preisen in Spalten zusammengefasst.
  SELECT FROM r_prodvaluationledgeraccounttp AS l
    FIELDS
      l~product,
      l~valuationarea,
      l~valuationtype,
      l~ledger,
      l~currency,
* Summe der Spalte MovingAveragePrice für Währungstyp 10
      SUM( CASE WHEN currencyrole = '10' THEN movingaverageprice ELSE dec`0.00` END ) AS price_10,
* Summe der Spalte MovingAveragePrice für Währungstyp 30
      SUM( CASE WHEN currencyrole = '30' THEN movingaverageprice ELSE dec`0.00` END ) AS price_30,
* Summe der Spalte MovingAveragePrice für Währungstyp 31
      SUM( CASE WHEN currencyrole = '31' THEN movingaverageprice ELSE dec`0.00` END ) AS price_31
    WHERE l~product       EQ @p_matnr
      AND l~valuationarea EQ @p_bwkey
      AND l~valuationtype EQ @p_bwtar
      AND l~ledger        EQ @p_ledger
* Gruppierung, damit die Summenbildung oben pro Gruppe (Produkt / Währungstyp) erfolgt
    GROUP BY l~product,
             l~valuationarea,
             l~valuationtype,
             l~ledger,
             l~currency,
             l~movingaverageprice
    ORDER BY l~product
    INTO TABLE @DATA(it_product).

  IF sy-subrc = 0.
* Daten im Inline-Browser im SAP-Fenster anzeigen
    cl_abap_browser=>show_html( EXPORTING title       = 'Pivot'
                                          html_string = cl_demo_output=>new( )->write_data( it_product )->get( )
                                          container   = cl_gui_container=>default_screen ).

* cl_gui_container=>default_screen erzwingen
    WRITE: space.
  ENDIF.

Variante 3 (SELECT, STRING_AGG, substring_regexpr, GROUP BY)

* Im Beispiel werden durch Anweisungen in einem SELECT-Statement mehrere Zeilen einer Rückgabemenge zu einer Zeile zusammengefasst (gruppiert).
* Kern der Funktion zur Umwandlung der Zeilenwerte in Spaltenwerte sind die Funktionen:
*  STRING_AGG( )
*  substring_regexpr( ) mit PCRE-RegEx
*  GROUP BY
 
* als Konstanten für die PCRE-RegEx sind nur elementare Datentypen (keine Strings) erlaubt (hier: CHAR)
* die Längen der Zeichenketten müssen exakt stimmen,
* sonst funktioniert das Pattern-Matching bei der RegEx nicht
* zum Testen der PCRE-RegEx siehe auch: https://regex101.com/
CONSTANTS: co_part1 TYPE char11 VALUE '^(\d+\.\d+)'.
CONSTANTS: co_part2 TYPE char21 VALUE '^\d+\.\d+\|\K\d+\.\d+'.
CONSTANTS: co_part3 TYPE char31 VALUE '^\d+\.\d+\|\d+\.\d+\|\K\d+\.\d+'.
 
PARAMETERS: p_matnr TYPE i_prodvalnledgeraccounttp_2-product DEFAULT '1122334455'.
PARAMETERS: p_bwkey TYPE i_prodvalnledgeraccounttp_2-valuationarea DEFAULT '0001'.
PARAMETERS: p_bwtar TYPE i_prodvalnledgeraccounttp_2-valuationtype DEFAULT ''.
PARAMETERS: p_ledger TYPE i_prodvalnledgeraccounttp_2-ledger DEFAULT '0L'.
 
START-OF-SELECTION.
* Suche der Periodischen Verrechnungspreise im Material Ledger über
* CDS-View I_PRODVALNLEDGERACCOUNTTP_2 (Product Valuation Ledger Account - TP)
* das SELECT liefert zeilenweise zu jedem Währungstyp einen Preis
* über STRING_AGG werden die Preise zu einer Gruppe mit Trennzeichen zusammengefasst
* mit substring_regexpr kann über eine RegEx dann ein Teil (erster, mittlerer oder letzter) dieser Gruppe extrahiert werden
  SELECT FROM i_prodvalnledgeraccounttp_2 AS pa
    FIELDS
      pa~product,
      pa~valuationarea,
      pa~ledger,
      pa~productpriceunitquantity,
      pa~currency,
      pa~standardprice,
      pa~companycode,
      pa~baseunit,
* Beispiel: periodische Verrechnungspreise als Gruppe zu einem String mit Trennzeichen "," zusammenfassen
      STRING_AGG( CAST( pa~movingaverageprice AS CHAR ), ', ' ) AS avg_price_all,
* Beispiel: periodische Verrechnungspreise als Gruppe zu einem String mit Trennzeichen "|" zusammenfassen
*           und über eine PCRE-RegEx den ersten Wert bis zum Trennzeichen "|" in eine Spalte avg_price1 schreiben
      substring_regexpr( pcre = @co_part1, value = STRING_AGG( CAST( pa~movingaverageprice AS CHAR ), '|' ) ) AS avg_price1,
* Beispiel: periodische Verrechnungspreise als Gruppe zu einem String mit Trennzeichen "|" zusammenfassen
*           und über eine PCRE-RegEx den mittleren Wert von "|" bis "|" in eine Spalte avg_price2 schreiben
      substring_regexpr( pcre = @co_part2, value = STRING_AGG( CAST( pa~movingaverageprice AS CHAR ), '|' ) ) AS avg_price2,
* Beispiel: periodische Verrechnungspreise als Gruppe zu einem String mit Trennzeichen "|" zusammenfassen
*           und über eine PCRE-RegEx den letzen Wert ab "|" in eine Spalte avg_price3 schreiben
      substring_regexpr( pcre = @co_part3, value = STRING_AGG( CAST( pa~movingaverageprice AS CHAR ), '|' ) ) AS avg_price3
    WHERE pa~product       EQ @p_matnr
      AND pa~valuationarea EQ @p_bwkey
      AND pa~valuationtype EQ @p_bwtar
      AND pa~ledger        EQ @p_ledger
    GROUP BY pa~product,
             pa~valuationarea,
             pa~ledger,
             pa~productpriceunitquantity,
             pa~currency,
             pa~standardprice,
             pa~companycode,
             pa~baseunit
    ORDER BY pa~product
    INTO TABLE @DATA(it_product).
 
  IF sy-subrc = 0.
    TRY.
* SALV-Table
        DATA: o_salv TYPE REF TO cl_salv_table.
 
        cl_salv_table=>factory( IMPORTING r_salv_table = o_salv
                                CHANGING  t_table      = it_product ).
 
* Grundeinstellungen
        o_salv->get_functions( )->set_all( abap_true ).
        o_salv->get_columns( )->set_optimize( abap_true ).
        o_salv->get_display_settings( )->set_list_header( 'Pivot' ).
        o_salv->get_display_settings( )->set_striped_pattern( abap_true ).
        o_salv->get_selections( )->set_selection_mode( if_salv_c_selection_mode=>row_column ).
 
* Spaltenüberschriften: technischer Name und Beschreibungstexte, Short Text und Medium Text leer lassen für Autosize
        LOOP AT o_salv->get_columns( )->get( ) ASSIGNING FIELD-SYMBOL(<c>).
          DATA(o_col) = <c>-r_column.
          o_col->set_short_text( || ).
          o_col->set_medium_text( || ).
          IF o_col->get_long_text( ) IS INITIAL.
            o_col->set_long_text( |{ o_col->get_columnname( ) }| ).
          ELSE.
            o_col->set_long_text( |{ o_col->get_long_text( ) }| ).
          ENDIF.
        ENDLOOP.
 
        o_salv->display( ).
      CATCH cx_root INTO DATA(e_txt).
        WRITE: / e_txt->get_text( ).
    ENDTRY.
  ENDIF.

Variante 4 (SELECT, UNION, COLLECT)

TYPES: BEGIN OF ty_s_product,
         product TYPE i_prodvalnledgeraccounttp_2-product,
* Platzhalter für die Periodischen Verrechnungspreise je Währungstyp
         p1      TYPE i_prodvalnledgeraccounttp_2-movingaverageprice,
         p2      TYPE i_prodvalnledgeraccounttp_2-movingaverageprice,
         p3      TYPE i_prodvalnledgeraccounttp_2-movingaverageprice,
       END OF ty_s_product.

TYPES: ty_it_product TYPE STANDARD TABLE OF ty_s_product WITH DEFAULT KEY.

PARAMETERS: p_matnr TYPE i_prodvalnledgeraccounttp_2-product DEFAULT '1122334455'.
PARAMETERS: p_bwkey TYPE i_prodvalnledgeraccounttp_2-valuationarea DEFAULT '0001'.
PARAMETERS: p_bwtar TYPE i_prodvalnledgeraccounttp_2-valuationtype DEFAULT ''.
PARAMETERS: p_ledger TYPE i_prodvalnledgeraccounttp_2-ledger DEFAULT '0L'.

START-OF-SELECTION.

  DATA: it_product_union TYPE ty_it_product.

* Suche der Periodischen Verrechnungspreise pro Währungstyp im Material Ledger über
* CDS-View I_PRODVALNLEDGERACCOUNTTP_2 (Product Valuation Ledger Account - TP)
* Die Datensätze pro Währungstyp werden hier über ein UNION-Statement zu einer Ausgabemenge
* zusammengeführt. Dabei werden die jeweils leeren Preise für p1, p2, p3 mit dec`0.00` vorbelegt,
* damit später bei COLLECT die Werte korrekt in einer Zeile verdichtet werden.

* erstes SELECT für Währungstyp '10'
  SELECT FROM i_prodvalnledgeraccounttp_2 AS pa
    FIELDS
        pa~product,
        pa~movingaverageprice AS p1,
        dec`0.00` AS p2,
        dec`0.00` AS p3
    WHERE pa~product       EQ @p_matnr
      AND pa~valuationarea EQ @p_bwkey
      AND pa~valuationtype EQ @p_bwtar
      AND pa~ledger        EQ @p_ledger
      AND pa~currencyrole  EQ '10'
  UNION
* zweites SELECT für Währungstyp '30'
  SELECT FROM i_prodvalnledgeraccounttp_2 AS pa
    FIELDS
        pa~product,
        dec`0.00` AS p1,
        pa~movingaverageprice AS p2,
        dec`0.00` AS p3
    WHERE pa~product       EQ @p_matnr
      AND pa~valuationarea EQ @p_bwkey
      AND pa~valuationtype EQ @p_bwtar
      AND pa~ledger        EQ @p_ledger
      AND pa~currencyrole  EQ '30'
  UNION
* drittes SELECT für Währungstyp '31'
  SELECT FROM i_prodvalnledgeraccounttp_2 AS pa
    FIELDS
        pa~product,
        dec`0.00` AS p1,
        dec`0.00` AS p2,
        pa~movingaverageprice AS p3
    WHERE pa~product       EQ @p_matnr
      AND pa~valuationarea EQ @p_bwkey
      AND pa~valuationtype EQ @p_bwtar
      AND pa~ledger        EQ @p_ledger
      AND pa~currencyrole  EQ '31'
  INTO CORRESPONDING FIELDS OF TABLE @it_product_union.

  IF sy-subrc = 0.
* Daten-Aufbereitung
    DATA: it_product_pivot TYPE HASHED TABLE OF ty_s_product WITH UNIQUE KEY product.

    LOOP AT it_product_union ASSIGNING FIELD-SYMBOL(<p>).
* COLLECT verdichtet gleiche Datensätze zu einem Datensatz, verwendet intern HASHED TABLES
* Zu beachten ist dabei, dass beim Verdichten die Zahlenwerte (int, fltp, dec usw.)
* der einzelnen Zeilen summiert werden!
      COLLECT <p> INTO it_product_pivot.
    ENDLOOP.

* Daten im Inline-Browser im SAP-Fenster anzeigen
    cl_abap_browser=>show_html( EXPORTING title       = 'Pivot'
                                          html_string = cl_demo_output=>new( )->write_data( it_product_union )->write_data( it_product_pivot )->get( )
                                          container   = cl_gui_container=>default_screen ).

* cl_gui_container=>default_screen erzwingen
    WRITE: space.

  ENDIF.

Links

[ABAP] Interne Tabellen: Schleifen mit FOR, THEN, WHILE, GROUPS, IN GROUP

Variante 1 (FOR … WHILE)

DATA(it_strings) = VALUE stringtab( ).

it_strings = VALUE #(
* For i = 1 To 10
                      FOR i = 1 WHILE i < 11
                      (
                        |{ i }|
                      )
                    ).

cl_demo_output=>display( it_strings ).

Variante 2 (FOR … WHILE mit Schrittweite)

                        
DATA(it_strings) = VALUE stringtab( ).

it_strings = VALUE #(
* For i = 1 To 10 Step 2
                      FOR i = 1 THEN i + 2 WHILE i < 11
                      (
                        |{ i }|
                      )
                    ).

cl_demo_output=>display( it_strings ).

Variante 3 (NESTED FOR)

                        
SELECT * FROM sflight INTO TABLE @DATA(it_sflight).
SELECT * FROM spfli INTO TABLE @DATA(it_spfli).

DATA(it_strings) = VALUE stringtab(
* alle Einträge aus sflight ab 01.01.2013
                                    FOR <f> IN it_sflight INDEX INTO idxf WHERE ( fldate >= '20130101' )
* alle Einträge aus spfli mit den Schlüsseln wie in der Ergebnismenge des vorherigen FOR
                                      FOR <c> IN it_spfli INDEX INTO idxc WHERE ( carrid = <f>-carrid AND connid = <f>-connid )
                                      (
* Ausgabe als Stringtab
                                        |{ idxf } \| { idxc } \| { <c>-carrid } \| { <c>-connid } \| { <c>-airpfrom }|
                                      )
                                  ).

cl_demo_output=>display( it_strings ).

Variante 4 (FOR GROUPS, FOR … IN GROUP)

TYPES: BEGIN OF ty_mat,
         matnr TYPE matnr,
         mtart TYPE mtart,
         price TYPE kbetr,
       END OF ty_mat.

TYPES: ty_it_mat TYPE HASHED TABLE OF ty_mat WITH UNIQUE KEY matnr
                                             WITH NON-UNIQUE SORTED KEY key_mtart COMPONENTS mtart.

TYPES: BEGIN OF ty_mat_sum,
         mtart TYPE mtart,
         count TYPE i,
         price TYPE kbetr,
       END OF ty_mat_sum.

TYPES: ty_it_mat_sum TYPE HASHED TABLE OF ty_mat_sum WITH UNIQUE KEY mtart.

* Tabelle mit Materialien
DATA(it_mat) = VALUE ty_it_mat(
                                ( matnr = '1' mtart = 'ROH'  price = '1.56' )
                                ( matnr = '2' mtart = 'ROH'  price = '2.00' )
                                ( matnr = '3' mtart = 'NLAG' price = '3.10' )
                                ( matnr = '4' mtart = 'NLAG' price = '0.40' )
                                ( matnr = '5' mtart = 'NLAG' price = '4.10' )
                                ( matnr = '6' mtart = 'HALB' price = '1.00' )
                                ( matnr = '7' mtart = 'HALB' price = '0.10' )
                              ).

* Tabelle gruppiert nach Materialarten ohne 'HALB' und summierten Preisen
DATA(it_mat_sum) = VALUE ty_it_mat_sum(
                                        FOR GROUPS grp OF <mtart> IN it_mat WHERE ( mtart NE 'HALB' ) GROUP BY ( mtart = <mtart>-mtart size = GROUP SIZE )
                                        (
                                          mtart = grp-mtart " Materialart der Gruppe
                                          count = grp-size  " Anz. Elemente der Gruppe
                                          price = REDUCE #( " Summe über die Elemente der akt. Gruppe bilden
                                                            INIT p = '0.00'
                                                            FOR <m> IN GROUP grp WHERE ( mtart = grp-mtart )
                                                            NEXT p = p + <m>-price
                                                          )
                                        )
                                      ).

cl_demo_output=>display( it_mat_sum ).

Links

[ABAP] Daten effizient zu internen Tabellen hinzufügen

Variante 1 (HASHED TABLE)

TYPES: BEGIN OF ty_msgnr,
         msgnr TYPE t100-msgnr,
       END OF ty_msgnr.

DATA: it_t100 TYPE HASHED TABLE OF t100 WITH UNIQUE KEY sprsl arbgb msgnr.

SELECT * FROM t100
  INTO TABLE @it_t100
  UP TO 100000 ROWS.

DATA: it_msgnr TYPE HASHED TABLE OF ty_msgnr WITH UNIQUE KEY msgnr.

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

LOOP AT it_t100 ASSIGNING FIELD-SYMBOL(<s>).
* jedes Element auf Vorhandensein Prüfen und ggf. zur Liste hinzufügen
  IF NOT line_exists( it_msgnr[ msgnr = <s>-msgnr ] ).
    INSERT VALUE ty_msgnr( msgnr = <s>-msgnr ) INTO TABLE it_msgnr.
  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'.

Variante 2 (GROUP BY)

TYPES: BEGIN OF ty_msgnr,
         msgnr TYPE t100-msgnr,
       END OF ty_msgnr.

*DATA: it_t100 TYPE HASHED TABLE OF t100 WITH UNIQUE KEY sprsl arbgb msgnr.
DATA: it_t100 TYPE STANDARD TABLE OF t100 WITH DEFAULT KEY.

SELECT * FROM t100
  INTO TABLE @it_t100
  UP TO 100000 ROWS.

DATA: it_msgnr TYPE STANDARD TABLE OF ty_msgnr WITH DEFAULT KEY.

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

LOOP AT it_t100 ASSIGNING FIELD-SYMBOL(<s>) GROUP BY <s>-msgnr ASCENDING.
* jedes Element auf Vorhandensein Prüfen und ggf. zur Liste hinzufügen
  APPEND VALUE ty_msgnr( msgnr = <s>-msgnr ) TO it_msgnr.
ENDLOOP.

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

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

Variante 3 (am schnellsten: COLLECT)

TYPES: BEGIN OF ty_msgnr,
         msgnr TYPE t100-msgnr,
       END OF ty_msgnr.

* Messages lesen
SELECT * FROM t100
  INTO TABLE @DATA(it_t100)
  UP TO 100000 ROWS.

DATA: it_msgnr TYPE STANDARD TABLE OF ty_msgnr WITH DEFAULT KEY.

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

LOOP AT it_t100 ASSIGNING FIELD-SYMBOL(<s>).
* jedes Element auf Vorhandensein Prüfen und ggf. zur Liste hinzufügen
* COLLECT verwendet intern HASHED TABLES
  COLLECT VALUE ty_msgnr( msgnr = <s>-msgnr ) INTO it_msgnr.
ENDLOOP.

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

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

[ABAP] Gruppierung von LOOP-Elementen (LOOP, GROUP BY), Group-Member

Beispiel 1 (einfach)

TYPES: BEGIN OF ty_person,
         person_id TYPE char3,
         name      TYPE char10,
         city      TYPE char3,
       END OF ty_person.

TYPES: ty_it_persons TYPE STANDARD TABLE OF ty_person WITH DEFAULT KEY.

DATA(it_persons) = VALUE ty_it_persons( ( person_id = 'P01' name = 'Horst' city = 'HAM' )
                                        ( person_id = 'P02' name = 'Udo'   city = 'BER' )
                                        ( person_id = 'P03' name = 'Inge'  city = 'HAM' )
                                        ( person_id = 'P04' name = 'Edit'  city = 'BER' )
                                        ( person_id = 'P05' name = 'Agnes' city = 'HAM' )
                                        ( person_id = 'P06' name = 'Ede'   city = 'LEI' ) ).

* Gruppierung nach City
* Aufsteigend sortiert
* mit Members (Unterelemente)
LOOP AT it_persons ASSIGNING FIELD-SYMBOL(<c>) GROUP BY <c>-city ASCENDING.

  LOOP AT GROUP <c> ASSIGNING FIELD-SYMBOL(<p>).
    WRITE: / '  ', <p>-person_id, <p>-name, <p>-city.
  ENDLOOP.

* neue interne Tabelle aus der Gruppe generieren
  DATA(it_cp) = VALUE ty_it_persons( FOR <cp> IN GROUP <c> ( <cp> ) ).

  ...

  ULINE.
ENDLOOP.

Beispiel 2 (WHERE, ASCENDING, ASSIGNING FIELD-SYMBOL)

TYPES: BEGIN OF ty_person,
         person_id TYPE char3,
         name      TYPE char10,
         city      TYPE char3,
         street    TYPE char20,
       END OF ty_person.

TYPES: ty_it_persons TYPE STANDARD TABLE OF ty_person WITH DEFAULT KEY.

* Testdaten mit Städten und Personen
DATA(it_persons) = VALUE ty_it_persons( ( person_id = 'P01' name = 'Horst' city = 'HAM' street = 'Allee' )
                                        ( person_id = 'P02' name = 'Udo'   city = 'BER' street = 'Kudamm' )
                                        ( person_id = 'P03' name = 'Inge'  city = 'HAM' street = 'Allee' )
                                        ( person_id = 'P04' name = 'Edit'  city = 'BER' street = 'Müllerstr.' )
                                        ( person_id = 'P05' name = 'Agnes' city = 'HAM' street = 'Hauptstraße' )
                                        ( person_id = 'P06' name = 'Ede'   city = 'LEI' street = 'Zentralplatz' )
                                        ( person_id = 'P07' name = 'Ida'   city = 'LEI' street = 'Zentralplatz' )
                                        ( person_id = 'P08' name = 'Ulf'   city = 'LEI' street = 'Poststraße' )
                                        ( person_id = 'P09' name = 'Ingo'  city = 'BER' street = 'Uferweg' )
                                        ( person_id = 'P10' name = 'Heinz' city = 'LEI' street = 'Postplatz' )
                                        ( person_id = 'P11' name = 'Heini' city = 'HAM' street = 'Postweg' )
                                      ).

* Suchkriterien für die Liste
DATA(rg_city) = VALUE rseloption( ( sign = 'I' option = 'EQ' low = 'HAM' high = '' )
                                  ( sign = 'I' option = 'EQ' low = 'LEI' high = '' ) ).

* Personenliste anhand der Suchkriterien für city durchloopen
LOOP AT it_persons ASSIGNING FIELD-SYMBOL(<g>) WHERE city IN rg_city
  GROUP BY ( city  = <g>-city       " Gruppierung nach feld 'city'
             size  = GROUP SIZE     " Anz. Elemente der akt. Gruppe in <gp>
             index = GROUP INDEX )  " Index der Gruppe <gp>
  ASCENDING AS TEXT                 " Gruppen aufsteigend sortieren
  ASSIGNING FIELD-SYMBOL(<gp>).     " Zugriff auf die Gruppe über <gp>

  WRITE: / |       City: { <gp>-city  }|.
  WRITE: / |GROUP INDEX: { <gp>-index }|.
  WRITE: / | GROUP SIZE: { <gp>-size }|.
  WRITE: / |   sy-tabix: { sy-tabix }|.

* Unterelemente der Gruppe durchloopen
  LOOP AT GROUP <gp> ASSIGNING FIELD-SYMBOL(<pi>).
    WRITE: / |     sy-tabix: { sy-tabix }|.
    WRITE: / |         data: { <pi>-city } { <pi>-person_id } { <pi>-name } { <pi>-street } |.
  ENDLOOP.

  ULINE.

ENDLOOP.

Beispiel 3 (Hierarchie)

TYPES: BEGIN OF ty_person,
         person_id TYPE char3,
         name      TYPE char10,
         city      TYPE char3,
         street    TYPE char20,
       END OF ty_person.

TYPES: ty_it_persons TYPE STANDARD TABLE OF ty_person WITH DEFAULT KEY.

* Testdaten mit Städten und Personen
DATA(it_persons) = VALUE ty_it_persons( ( person_id = 'P01' name = 'Horst' city = 'HAM' street = 'Allee' )
                                        ( person_id = 'P02' name = 'Udo'   city = 'BER' street = 'Kudamm' )
                                        ( person_id = 'P03' name = 'Inge'  city = 'HAM' street = 'Allee' )
                                        ( person_id = 'P04' name = 'Edit'  city = 'BER' street = 'Müllerstr.' )
                                        ( person_id = 'P05' name = 'Agnes' city = 'HAM' street = 'Hauptstraße' )
                                        ( person_id = 'P06' name = 'Ede'   city = 'LEI' street = 'Zentralplatz' )
                                        ( person_id = 'P07' name = 'Ida'   city = 'LEI' street = 'Zentralplatz' )
                                        ( person_id = 'P08' name = 'Ulf'   city = 'LEI' street = 'Poststraße' )
                                        ( person_id = 'P09' name = 'Ingo'  city = 'BER' street = 'Uferweg' )
                                        ( person_id = 'P10' name = 'Heinz' city = 'LEI' street = 'Postplatz' )
                                        ( person_id = 'P11' name = 'Heini' city = 'HAM' street = 'Postweg' )
                                      ).

* Gruppierung nach City
* Aufsteigend sortiert
* mit Members (Unterelemente)
LOOP AT it_persons ASSIGNING FIELD-SYMBOL(<c>) GROUP BY <c>-city ASCENDING.
* Stadt ausgeben
  WRITE: / <c>-city.

* Gruppierung nach City->Straße
* Aufsteigend sortiert
* mit Members (Unterelemente)
  LOOP AT GROUP <c> ASSIGNING FIELD-SYMBOL(<s>) GROUP BY <s>-street ASCENDING.
    WRITE: / '  ', <s>-street.

    LOOP AT GROUP <s> ASSIGNING FIELD-SYMBOL(<p>).
      WRITE: / '    ', <p>-person_id, <p>-name.
    ENDLOOP.
  ENDLOOP.

  ULINE.
ENDLOOP.

Beispiel 4 (REFERENCE INTO mit und ohne MEMBERS)

TYPES: BEGIN OF ty_person,
         person_id TYPE char3,
         name      TYPE char10,
         city      TYPE char3,
       END OF ty_person.

* Sortierte Tabelle
TYPES: ty_it_persons TYPE SORTED TABLE OF ty_person WITH UNIQUE KEY person_id.

DATA(it_persons) = VALUE ty_it_persons( ( person_id = 'P01' name = 'Horst' city = 'HAM' )
                                        ( person_id = 'P02' name = 'Udo'   city = 'BER' )
                                        ( person_id = 'P03' name = 'Inge'  city = 'HAM' )
                                        ( person_id = 'P04' name = 'Edit'  city = 'BER' )
                                        ( person_id = 'P05' name = 'Agnes' city = 'HAM' )
                                        ( person_id = 'P06' name = 'Ede'   city = 'LEI' ) ).

* Gruppierung nach City
* Aufsteigend sortiert
* WITHOUT MEMBERS -> bessere Performance, aber kein Zugriff auf Unterelemente
LOOP AT it_persons ASSIGNING FIELD-SYMBOL(<c>)
  GROUP BY ( city  = <c>-city
             size  = GROUP SIZE
             index = GROUP INDEX )
  ASCENDING
  WITHOUT MEMBERS
  REFERENCE INTO DATA(o_city).

  WRITE: / |City: { o_city->city }, Size: { o_city->size }, Index: { o_city->index }|.
ENDLOOP.

ULINE.

* Gruppierung nach City
* Aufsteigend sortiert
* mit Members (Unterelemente)
LOOP AT it_persons ASSIGNING FIELD-SYMBOL(<c2>)
  GROUP BY ( city  = <c2>-city
             size  = GROUP SIZE
             index = GROUP INDEX )
  ASCENDING
  REFERENCE INTO DATA(o_city2).

  WRITE: / |City: { o_city2->city }, Size: { o_city2->size }, Index: { o_city2->index }|.

  LOOP AT GROUP o_city2 ASSIGNING FIELD-SYMBOL(<p>).
    WRITE: / '  ', <p>-person_id, <p>-name, <p>-city.
  ENDLOOP.
ENDLOOP.

Weiterführende Infos: Link und Link

[ABAP] OpenSQL: Verknüpfung von Tabelleninhalten über INNER JOIN, Zählung einzelner Datensatzgruppen (COUNT)

TYPES: BEGIN OF ty_ttxid,
         tdobject TYPE ttxid-tdobject,
         tdid     TYPE ttxid-tdid,
         tdtext   TYPE ttxit-tdtext,
         count    TYPE i,
       END OF ty_ttxid.
       
DATA: it_ttxid TYPE STANDARD TABLE OF ty_ttxid WITH DEFAULT KEY.

* count ist die Zählvariable für die Elemente der Gruppe, welche unter GROUP BY definiert wurde
* es werden nur Datensätze gezählt, die auch vorhanden sind, d.h. es gibt keine Werte mit count = 0
SELECT ttxid~tdobject, ttxid~tdid, ttxit~tdtext, COUNT( * ) AS count
  INTO CORRESPONDING FIELDS OF TABLE @it_ttxid
  FROM ttxid
  INNER JOIN ttxit ON ( ttxid~tdobject = ttxit~tdobject AND ttxid~tdid = ttxit~tdid )
  INNER JOIN stxh ON ( stxh~tdobject = ttxid~tdobject AND stxh~tdid = ttxid~tdid )
  WHERE ttxid~tdobject LIKE @p_obj
    AND ttxid~tdid LIKE @p_tdid
    AND stxh~tdfdate IN @so_date
    AND ttxit~tdspras = @p_lang
  GROUP BY ttxid~tdobject, ttxid~tdid, ttxit~tdtext.