[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] Retourenumlagerungsbestellungen lesen

* Bestellung
PARAMETERS: p_ebeln TYPE ekko-ebeln.

START-OF-SELECTION.

* Retourenumlagerungsbestellungen zur Bestellung lesen
  SELECT FROM ekko AS e
    INNER JOIN ekpo AS p ON ( e~ebeln EQ p~ebeln )
* Verwendung von CAST und substring, da Belegposition unterschiedliche Typen (NUMC5 und NUMC10) verwendet
    INNER JOIN msr_d_executed AS r ON ( p~ebeln EQ r~ref_doc_nr ) AND ( CAST( p~ebelp AS CHAR ) EQ substring( CAST( r~ref_doc_item AS CHAR ), 6, 5 ) )
  FIELDS DISTINCT
      e~ebeln,
      p~ebelp,
      p~matnr,
      p~txz01,
      p~menge,
      p~meins,
      p~netpr,
      r~doc_nr,
      r~doc_item
  WHERE e~ebeln EQ @p_ebeln
  INTO TABLE @DATA(it_ebeln).

  IF sy-subrc = 0.
    cl_demo_output=>write_data( it_ebeln ).
    DATA(lv_html) = cl_demo_output=>get( ).
    cl_abap_browser=>show_html( EXPORTING title       = 'Daten'
                                          html_string = lv_html
                                          container   = cl_gui_container=>default_screen ).

    WRITE: space.
  ENDIF.

Links

[ABAP] Excel-Datei (*.xlsx) in interne Tabelle laden (cl_fdt_xl_spreadsheet)

Variante 1 (generische Ausgabe mit cl_demo_output)

* Mit Hilfe der Klasse cl_fdt_xl_spreadsheet können über XML-Transformationen
* aus XLSX-Dateien Daten extrahiert und in eine interne Tabelle konvertiert werden
TRY.
    DATA: lv_rc TYPE i.
    DATA: it_files TYPE filetable.
    DATA: lv_action TYPE i.

* FileOpen-Dialog aufrufen
    cl_gui_frontend_services=>file_open_dialog( EXPORTING
                                                  file_filter = |xlsx (*.xlsx)\|*.xlsx\|{ cl_gui_frontend_services=>filetype_all }|
                                                CHANGING
                                                  file_table  = it_files
                                                  rc          = lv_rc
                                                  user_action = lv_action ).

    IF lv_action = cl_gui_frontend_services=>action_ok.
* wenn Datei ausgewählt wurde
      IF lines( it_files ) > 0.
* ersten Tabelleneintrag lesen
        DATA: lv_filesize TYPE w3param-cont_len.
        DATA: lv_filetype TYPE w3param-cont_type.
        DATA: it_bin_data TYPE w3mimetabtype.

* Bild auf Appl. Server hochladen (binary)
        cl_gui_frontend_services=>gui_upload( EXPORTING
                                                filename   = |{ it_files[ 1 ]-filename }|
                                                filetype   = 'BIN'
                                              IMPORTING
                                                filelength = lv_filesize
                                              CHANGING
                                                data_tab   = it_bin_data ).

* solix -> xstring
        DATA(lv_bin_data) = cl_bcs_convert=>solix_to_xstring( it_solix = it_bin_data ).

* Excel (itab) -> XML -> Ref-Objekt
* Achtung: Speicherintensiv und rel. langsam! Es sollten keine großen Datenmengen verarbeitet werden.
        DATA(o_excel) = NEW cl_fdt_xl_spreadsheet( document_name = CONV #( it_files[ 1 ]-filename )
                                                   xdocument     = lv_bin_data ).

        DATA: it_worksheet_names TYPE if_fdt_doc_spreadsheet=>t_worksheet_names.

* Worksheetnamen ermitteln
        o_excel->if_fdt_doc_spreadsheet~get_worksheet_names( IMPORTING worksheet_names = it_worksheet_names ).

        IF lines( it_worksheet_names ) > 0.
* erste Worksheet holen und -> REF to itab erstellen
          DATA(o_worksheet_itab) = o_excel->if_fdt_doc_spreadsheet~get_itab_from_worksheet( it_worksheet_names[ 1 ] ).

* Referenz auf generisches Feldsymbol mappen
          ASSIGN o_worksheet_itab->* TO FIELD-SYMBOL(<worksheet>).

* Datenausgabe
          cl_demo_output=>write_data( <worksheet> ).

* HTML-Daten aus itab generieren
          DATA(lv_html) = cl_demo_output=>get( ).

* Daten im Inline-Browser im SAP-Fenster anzeigen
          cl_abap_browser=>show_html( EXPORTING
                                        title        = 'Excel-Worksheet'
                                        html_string  = lv_html
                                        container    = cl_gui_container=>default_screen ).

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

  CATCH cx_root INTO DATA(e_text).
    MESSAGE e_text->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
ENDTRY.

Variante 2 (generische Listausgabe)

TYPES : BEGIN OF ty_s_col_width,
          width TYPE i,
        END OF ty_s_col_width.

TYPES: ty_it_col_width TYPE STANDARD TABLE OF ty_s_col_width WITH DEFAULT KEY.

* Spaltenbreiten für Tabellenausgabe
DATA(it_col_width) = VALUE ty_it_col_width(
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                            ( width = 30 )
                                          ).

* Mit Hilfe der Klasse cl_fdt_xl_spreadsheet können über XML-Transformationen
* aus XLSX-Dateien Daten extrahiert und in eine interne Tabelle konvertiert werden
TRY.
    DATA: lv_rc TYPE i.
    DATA: it_files TYPE filetable.
    DATA: lv_action TYPE i.

* FileOpen-Dialog aufrufen
    cl_gui_frontend_services=>file_open_dialog( EXPORTING
                                                  file_filter = |xlsx (*.xlsx)\|*.xlsx\|{ cl_gui_frontend_services=>filetype_all }|
                                                CHANGING
                                                  file_table  = it_files
                                                  rc          = lv_rc
                                                  user_action = lv_action ).

    IF lv_action = cl_gui_frontend_services=>action_ok.
* wenn Datei ausgewählt wurde
      IF lines( it_files ) > 0.
* ersten Tabelleneintrag lesen
        DATA: lv_filesize TYPE w3param-cont_len.
        DATA: lv_filetype TYPE w3param-cont_type.
        DATA: it_bin_data TYPE w3mimetabtype.

* Bild auf Appl. Server hochladen (binary)
        cl_gui_frontend_services=>gui_upload( EXPORTING
                                                filename   = |{ it_files[ 1 ]-filename }|
                                                filetype   = 'BIN'
                                              IMPORTING
                                                filelength = lv_filesize
                                              CHANGING
                                                data_tab   = it_bin_data ).

* solix -> xstring
        DATA(lv_bin_data) = cl_bcs_convert=>solix_to_xstring( it_solix = it_bin_data ).

* Excel (itab) -> XML -> Ref-Objekt
* Achtung: Speicherintensiv und rel. langsam! Es sollten keine großen Datenmengen verarbeitet werden.
        DATA(o_excel) = NEW cl_fdt_xl_spreadsheet( document_name = CONV #( it_files[ 1 ]-filename )
                                                   xdocument     = lv_bin_data ).

        DATA: it_worksheet_names TYPE if_fdt_doc_spreadsheet=>t_worksheet_names.

* Worksheetnamen ermitteln
        o_excel->if_fdt_doc_spreadsheet~get_worksheet_names( IMPORTING worksheet_names = it_worksheet_names ).

        IF lines( it_worksheet_names ) > 0.
* erste Worksheet holen und -> REF to itab erstellen
          DATA(o_worksheet_itab) = o_excel->if_fdt_doc_spreadsheet~get_itab_from_worksheet( it_worksheet_names[ 1 ] ).

* Referenz auf generisches Feldsymbol mappen
          FIELD-SYMBOLS: <worksheet> TYPE ANY TABLE.
          ASSIGN o_worksheet_itab->* TO <worksheet>.

* Tabellen-Zeile erzeugen
* muss hier erfolgen, damit man ein "greifbares" Tabellen-Zeilen-Objekt
* für die Strukturermittlung (describe_by_data) hat
          DATA: o_row TYPE REF TO data.
          CREATE DATA o_row LIKE LINE OF <worksheet>.
          ASSIGN o_row->* TO FIELD-SYMBOL(<row>).

* Komponenten (Spalten) einer Tabellenzeile ermitteln
          DATA(o_struct) = CAST cl_abap_structdescr( cl_abap_typedescr=>describe_by_data( <row> ) ).
          DATA(it_comp_tab) = o_struct->get_components( ).

* Anzahl Spalten der Tabellen-Zeile holen
          DATA(lv_colcnt) = lines( it_comp_tab ).

* Worksheet durchloopen
          LOOP AT <worksheet> ASSIGNING <row>.

* Spalten der akt. Zeile durchgehen
            DO lv_colcnt TIMES.
* Zelle: n-tes Element der akt. Zeile holen
              ASSIGN COMPONENT sy-index OF STRUCTURE <row> TO FIELD-SYMBOL(<cell>).

* Trennzeichen vor die Spalte einfügen, wenn nicht 1. Spalte
              IF sy-index > 1.
                WRITE: '|'.
              ENDIF.

* Achtung: Zell-Typ beachten! Es können hier nur flache Typen (Keine Strukturen, Tabellen) ausgegeben werden, der Rest muss gesondert behandelt werden
              IF CAST cl_abap_elemdescr( it_comp_tab[ sy-index ]-type )->kind = cl_abap_elemdescr=>kind_elem.
* Ausgabe Zellinhalt mit vordefinierter Spaltenbreite
                WRITE: |{ <cell> WIDTH = it_col_width[ sy-index ]-width }|.
              ENDIF.
            ENDDO.

            NEW-LINE.
          ENDLOOP.
        ENDIF.
      ENDIF.
    ENDIF.

  CATCH cx_root INTO DATA(e_text).
    MESSAGE e_text->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
ENDTRY.

Variante 3 (generische Ausgabe im SALV-Grid)

* Mit Hilfe der Klasse cl_fdt_xl_spreadsheet können über XML-Transformationen
* aus XLSX-Dateien Daten extrahiert und in eine interne Tabelle konvertiert werden
TRY.
    DATA: lv_rc TYPE i.
    DATA: it_files TYPE filetable.
    DATA: lv_action TYPE i.

* FileOpen-Dialog aufrufen
    cl_gui_frontend_services=>file_open_dialog( EXPORTING
                                                  file_filter = |xlsx (*.xlsx)\|*.xlsx\|{ cl_gui_frontend_services=>filetype_all }|
                                                CHANGING
                                                  file_table  = it_files
                                                  rc          = lv_rc
                                                  user_action = lv_action ).

    IF lv_action = cl_gui_frontend_services=>action_ok.
* wenn Datei ausgewählt wurde
      IF lines( it_files ) > 0.
* ersten Tabelleneintrag lesen
        DATA: lv_filesize TYPE w3param-cont_len.
        DATA: lv_filetype TYPE w3param-cont_type.
        DATA: it_bin_data TYPE w3mimetabtype.

* Bild auf Appl. Server hochladen (binary)
        cl_gui_frontend_services=>gui_upload( EXPORTING
                                                filename   = |{ it_files[ 1 ]-filename }|
                                                filetype   = 'BIN'
                                              IMPORTING
                                                filelength = lv_filesize
                                              CHANGING
                                                data_tab   = it_bin_data ).

* solix -> xstring
        DATA(lv_bin_data) = cl_bcs_convert=>solix_to_xstring( it_solix = it_bin_data ).

* Excel (itab) -> XML -> Ref-Objekt
* Achtung: Speicherintensiv und rel. langsam! Es sollten keine großen Datenmengen verarbeitet werden.
        DATA(o_excel) = NEW cl_fdt_xl_spreadsheet( document_name = CONV #( it_files[ 1 ]-filename )
                                                   xdocument     = lv_bin_data ).

        DATA: it_worksheet_names TYPE if_fdt_doc_spreadsheet=>t_worksheet_names.

* Worksheetnamen ermitteln
        o_excel->if_fdt_doc_spreadsheet~get_worksheet_names( IMPORTING worksheet_names = it_worksheet_names ).

        IF lines( it_worksheet_names ) > 0.
* erste Worksheet holen und -> REF to itab erstellen
          DATA(o_worksheet_itab) = o_excel->if_fdt_doc_spreadsheet~get_itab_for_alv_update( ).

* Referenz auf generisches Feldsymbol mappen
          ASSIGN o_worksheet_itab->* TO FIELD-SYMBOL(<worksheet>).

          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        = <worksheet> ).

* 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( 'Worksheet' ).
              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
              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( || ).
                o_col->set_long_text( |{ o_col->get_columnname( ) }| ).
              ENDLOOP.

              o_salv->display( ).
            CATCH cx_root INTO DATA(e_txt).
              WRITE: / e_txt->get_text( ).
          ENDTRY.
        ENDIF.
      ENDIF.
    ENDIF.

  CATCH cx_root INTO DATA(e_text).
    MESSAGE e_text->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
ENDTRY.

[ABAP] NativeSQL: Zugriff auf eine Oracle-Datenbank

* DBCO: Transaktion zur Pflege der Datenbankverbindung zur Oracle-DB
* DBACOCKPIT
* DBCON: Tabelle der Datenbankverbindungen

* http://www.saptechnical.com/Tutorials/Basis/Oracle/RetrieveData.htm
* http://sqlines.com/oracle-to-sql-server/to_char_datetime
* https://archive.sap.com/discussions/thread/33542
* https://archive.sap.com/discussions/thread/107384
* https://blogs.sap.com/2012/12/28/native-sql-its-use-with-database-connection-in-sap/
* https://help.sap.com/http.svc/rc/abapdocu_750_index_htm/7.50/de-DE/abapexec_connection.htm

* Beispieldatentyp für verschiedene Oracle-Datentypen
TYPES: BEGIN OF ty_s_tableline,
         col_number     TYPE i,                      " NUMBER
         col_number10_2 TYPE p LENGTH 10 DECIMALS 2, " NUMBER (10,2)
         col_number5    TYPE p LENGTH 5 DECIMALS 0,  " NUMBER (5)
         col_char       TYPE c LENGTH 16,            " VARCHAR2 (16 Char)
         col_date_conv  TYPE c LENGTH 19,            " DATE ('YYYY-MM-DD HH24:MI:SS')
         col_date_conv2 TYPE c LENGTH 15,            " DATE ('YYYYMMDD HH24MISS')
       END OF ty_s_tableline.

TYPES: ty_it_table TYPE STANDARD TABLE OF ty_s_tableline WITH DEFAULT KEY.

* DB Connection zur Oracle DB
PARAMETERS: p_dbs TYPE dbcon-con_name DEFAULT 'MYDBCON'.
PARAMETERS: p_par1 TYPE matnr DEFAULT '12345'.

START-OF-SELECTION.

* Verbindungsinfos zur DB Connection lesen
  DATA: lv_dbtype TYPE dbcon_dbms.

  SELECT SINGLE dbms
    INTO @lv_dbtype
    FROM dbcon
    WHERE con_name = @p_dbs.

  IF sy-subrc = 0.
* Repräsentiert die Connection eine Oracle-DB?
    IF lv_dbtype = 'ORA'. " Domäne DBCON_DBMS

      TRY.
* Prüfen, ob die DB-Connection schon offen ist
          EXEC SQL.
            SET CONNECTION :p_dbs
          ENDEXEC.

          IF sy-subrc <> 0.
* Connect to DB
            EXEC SQL.
              CONNECT TO :p_dbs
            ENDEXEC.
          ENDIF.

          IF sy-subrc = 0.
* Session-Parameter setzen
            EXEC SQL.
              alter session set NLS_DATE_FORMAT = 'YYYYMMDD'
            ENDEXEC.

            EXEC SQL.
              alter session set NLS_NUMERIC_CHARACTERS = ',.'
            ENDEXEC.

* Open Cursor and SELECT
* SQL-Statements im ORACLE Syntax!
* Abbildung verschiedener Oracle-Datentypen als ABAP-Datentypen
* speziell das Datum (Orycle Typ DATE) muss Oracleseitig als Character-Feld (z.B. Länge 19 oder 15) interpretiert werden (CAST), damit es korrekt an SAP übertragen werden kann
* zusätzliche Komplexität durch INNER JOIN von zwei Tabellen mytab und mytab2
* Übergabe von Parametern an die EXEC SQL-Bereiche erfolgt immer durch ":Variablenname"
            EXEC SQL.
              OPEN dbcur FOR
              SELECT mytab.col_number,
                     mytab.col_number10_2,
                     mytab2.col_number5,
                     mytab.col_char,
                     CAST (TO_CHAR (mytab.col_date, 'YYYY-MM-DD HH24:MI:SS') AS VARCHAR2 (19)) AS col_date_conv
                     CAST (TO_CHAR (mytab.col_date, 'YYYYMMDD HH24MISS') AS VARCHAR2 (15)) AS col_date_conv2
              FROM myschemaname.mytablename mytab
              INNER JOIN myschemaname.mytablename2 mytab2 ON mytab.key1 = mytab2.key1 AND mytab.key2 = mytab2.key2
              WHERE mytab2.colxyz = :p_par1
            ENDEXEC.

            IF sy-subrc = 0.
              DATA: lv_line TYPE ty_s_tableline.
              DATA: it_table TYPE ty_it_table.

* Loop Cursor Data
              DO.
* DB-Cursor auf nächsten Datensatz setzen, solange, wie Daten vorhanden sind
                EXEC SQL.
                  FETCH NEXT dbcur INTO :lv_line
                ENDEXEC.
                IF sy-subrc = 0.
* Daten an itab anfügen
                  APPEND lv_line TO it_table.
                ELSE.
* keine Daten mehr vorhanden (EOF)
                  EXIT.
                ENDIF.
              ENDDO.

* Close Cursor
              EXEC SQL.
                CLOSE dbcur
              ENDEXEC.

            ENDIF.

* Reset auf "default connection"
            EXEC SQL.
              SET CONNECTION DEFAULT
            ENDEXEC.

* Disconnect
            EXEC SQL.
              DISCONNECT :p_dbs
            ENDEXEC.

* Daten ausgeben
            DATA: o_salv TYPE REF TO cl_salv_table.

            cl_salv_table=>factory( IMPORTING
                                      r_salv_table = o_salv
                                    CHANGING
                                      t_table      = it_table ).

            o_salv->get_functions( )->set_all( abap_true ).
            o_salv->get_columns( )->set_optimize( abap_true ).
            o_salv->get_display_settings( )->set_list_header( 'Oracle Data' ).
            o_salv->get_display_settings( )->set_striped_pattern( abap_true ).
            o_salv->get_selections( )->set_selection_mode( if_salv_c_selection_mode=>row_column ).

            LOOP AT o_salv->get_columns( )->get( ) ASSIGNING FIELD-SYMBOL(<c>).
              <c>-r_column->set_short_text( |{ <c>-r_column->get_columnname( ) }| ).
              <c>-r_column->set_medium_text( |{ <c>-r_column->get_columnname( ) }| ).
              <c>-r_column->set_long_text( |{ <c>-r_column->get_columnname( ) }| ).
            ENDLOOP.

            o_salv->display( ).
          ELSE.
            WRITE: / 'DB-Verbindung konnte nicht hergestellt werden.'.
          ENDIF.
        CATCH cx_root INTO DATA(e_txt).
          WRITE: / e_txt->get_text( ).
      ENDTRY.

    ELSE.
      WRITE: / |Falscher Datenbanktyp: { lv_dbtype }|.
    ENDIF.
  ELSE.
    WRITE: / |DB-Alias { p_dbs } nicht im System vorhanden.|.
  ENDIF.

[ABAP] RTTS: Interne Tabelle dynamisch erzeugen

Variante 1 (aus DDIC-Struktur)

TRY.
* Strukturdeskriptor für Komponenten (Spalten) der Tabelle anhand einer vorhandenen Struktur erzeugen
    DATA(o_struct_desc) = cl_abap_structdescr=>describe_by_name( 'SFLIGHTS' ).

* Tabellendeskriptor
    DATA(o_table_desc) = cl_abap_tabledescr=>create(
                                                     p_line_type  = CAST #( o_struct_desc )           " Spalten
                                                     p_table_kind = cl_abap_tabledescr=>tablekind_std " Tabellentyp STANDARD TABLE
                                                     p_unique     = abap_false                        " NON-UNIQUE KEY
                                                   ).

* Tabellenobjekt anhand des Tabellendeskriptors erstellen
    DATA: o_table TYPE REF TO data.
    CREATE DATA o_table TYPE HANDLE o_table_desc.

* Feldsymbol auf das Tabellenobjekt
    FIELD-SYMBOLS <table> TYPE ANY TABLE.
    ASSIGN o_table->* TO <table>.

* Daten holen und in Feldsymbol schreiben
    SELECT carrid, connid, carrname, fldate FROM sflights INTO CORRESPONDING FIELDS OF TABLE @<table>.

* Datenausgabe
    cl_demo_output=>display( <table> ).

  CATCH cx_root INTO DATA(e_txt).
    WRITE: / e_txt->get_text( ).
ENDTRY.

Variante 2 (eigene Strukturkomponenten)

TRY.
* Dummy für Datadescriptor
    DATA: lv_fldate TYPE sflights-fldate.

* Komponenten (Spalten) der Tabelle
    DATA(it_components) = VALUE cl_abap_structdescr=>component_table(
* Erzeugung über direkte Typangabe, abgeleitet aus dem Typ des eigentlichen Datenelements
* CHAR 3
                                                                      (
                                                                        name = 'CARRID'
                                                                        type = cl_abap_elemdescr=>get_c( 3 ) " S_CARR_ID
                                                                      )
* NUMC 4
                                                                      (
                                                                        name = 'CONNID'
                                                                        type = cl_abap_elemdescr=>get_n( 4 ) " S_CONN_ID
                                                                      )
* Erzeugung über Namen des Datenelements
                                                                      (
                                                                        name = 'CARRNAME'
                                                                        type = CAST #( cl_abap_elemdescr=>describe_by_name( 'S_CARRNAME' ) )
                                                                      )
* Erzeugung über Dummy-Datenobjekt
                                                                      (
                                                                        name = 'FLDATE'
                                                                        type = cast #( cl_abap_datadescr=>describe_by_data( lv_fldate ) )
                                                                      )
                                                                    ).

* Strukturdeskriptor für Komponententabelle
    DATA(o_struct_desc) = cl_abap_structdescr=>create( it_components ).

* Tabellendeskriptor
    DATA(o_table_desc) = cl_abap_tabledescr=>create(
                                                     p_line_type  = o_struct_desc                       " Spalten
                                                     p_table_kind = cl_abap_tabledescr=>tablekind_std   " Tabellentyp STANDARD TABLE
                                                     p_unique     = abap_false                          " NON-UNIQUE KEY
                                                     p_key        = VALUE #(                            " CARRID, CONNID als KEY
                                                                             ( name = 'CARRID' )
                                                                             ( name = 'CONNID' )
                                                                           )
                                                     p_key_kind   = cl_abap_tabledescr=>keydefkind_user " Benutzerdefinierter Schlüssel
                                                   ).

* Tabellenobjekt anhand des Tabellendeskriptors erstellen
    DATA: o_table TYPE REF TO data.
    CREATE DATA o_table TYPE HANDLE o_table_desc.

* Feldsymbol auf das Tabellenobjekt
    FIELD-SYMBOLS <table> TYPE ANY TABLE.
    ASSIGN o_table->* TO <table>.

* Daten holen und in Feldsymbol schreiben
    SELECT carrid, connid, carrname, fldate FROM sflights INTO CORRESPONDING FIELDS OF TABLE @<table>.

* Datenausgabe
    cl_demo_output=>display( <table> ).

  CATCH cx_root INTO DATA(e_txt).
    WRITE: / e_txt->get_text( ).
ENDTRY.

[ABAP] Vergleich Feldsymbole (FIELD-SYMBOLS) und Datenreferenzen (REF TO)

Feldsymbole (FIELD-SYMBOLS)

  • Alias / Referenz auf Datenbereich
  • im Ggs. zu Referenzen nur Wertvergleich möglich, kein Vergleich der Referenz
  • Zuweisung erfolgt zur Laufzeit
  • Casting möglich: ASSIGN … TO CASTING TYPE typename.

Datenreferenzen (REF TO)

  • Pointer auf Datenobjekte
  • im Ggs. zu Feldsymbolen ist der Vergleich von Referenzen und Werten möglich
  • typisiert: … TYPE REF TO typename.
  • untypisiert: … TYPE REF TO data.
  • dynamische Speicherallokation über NEW
  • Casting möglich: CAST, ?=
  • Zurgiff erfolgt immer über Dereferenzierung (->, ->*)

[ABAP] Objekte über die Systemklasse CL_OS_STATE clonen (kopieren)

* die Klasse, deren Objekte geclont werden sollen, muss von CL_OS_STATE
* ableiten, damit das Interface IF_OS_CLONE implementiert wird
CLASS lcl_clone_test DEFINITION INHERITING FROM cl_os_state.

  PUBLIC SECTION.
* ALIAS für die Implementierung von cl_os_state=>if_os_clone~clone( )
* ruft SYSTEM-CALL OBJMGR CLONE me TO result.
    ALIASES: clone FOR if_os_clone~clone.

    METHODS:
      constructor
        IMPORTING i_text TYPE string.
    METHODS:
      get_text
        RETURNING VALUE(rv_text) TYPE string.

  PRIVATE SECTION.
    DATA:
      gv_text TYPE string.

ENDCLASS.

CLASS lcl_clone_test IMPLEMENTATION.

  METHOD constructor.
* Konstruktor der Basisklasse CL_OS_STATE rufen
    super->constructor( ).
    gv_text = i_text.
  ENDMETHOD.

  METHOD get_text.
    rv_text = gv_text.
  ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.
  DATA(o_src) = NEW lcl_clone_test( 'SRC' ).
  WRITE: / o_src->get_text( ).

* Objekt O_SRC clonen, Rückgabe von REF TO object nach LCL_CLONE_TEST explizit casten
* es wird ein neues, zu O_SRC identisches, Objekt erzeugt
  DATA(o_clone) = CAST lcl_clone_test( o_src->clone( ) ).
  WRITE: / o_clone->get_text( ).

[ABAP] Arbeit mit Referenzen

einfache Wertänderung über Referenz

* int-Variable anlegen, Wert 1
DATA(lv_int) = 1.
* Referenz auf int
DATA(o_int) = REF #( lv_int ).

* Änderung auf Wert 2
lv_int = 2.

* Ausgabe Wert 2
WRITE: / o_int->*.

* Änderung auf 3
o_int->* = 3.

* Ausgabe Wert 3
WRITE: / lv_int.

mehrfache Wertänderung über Referenz

* int-Variable anlegen, Wert 1
DATA(lv_int) = 1.
* generische Referenz auf die int-Variable
DATA(o_int) = REF data( lv_int ).
* Variable auf Wert 2 ändern
lv_int = 2.

* Zwei Feldsymbole (<i1> und <i2>) mit der Referenz verknüpfen
ASSIGN o_int->* TO FIELD-SYMBOL(<i1>).
ASSIGN o_int->* TO FIELD-SYMBOL(<i2>).

* den Wert eines der Feldsymbole ändern
<i1> = 4.

* es ändern sich durch den Bezug sogleich alle anderen Feldsymbole und Variablen mit :)
WRITE: / lv_int.
WRITE: / <i1>.
WRITE: / <i2>.

generische Referenz auf interne Tabelle

* Stringtable aus DDIC (gefüllt)
DATA(it_stringtab) = VALUE stringtab( ( |Udo| )
                                      ( |Heinz| )
                                      ( |Klaus| ) ).

* generische Referenz auf die Stringtable
DATA(o_tab) = REF data( it_stringtab ).

* Feldsymbol explizit als generische Table definieren
FIELD-SYMBOLS: <tab> TYPE ANY TABLE.
* Feldsymbol auf die interne Tabelle mit der generischen Referenz verknüpfen
ASSIGN o_tab->* TO <tab>.

* Tabelleninhalt darstellen
LOOP AT <tab> ASSIGNING FIELD-SYMBOL(<l>).
  WRITE: / <l>.
ENDLOOP.

referentieller Zugriff auf eine interne Tabelle

* Typdeklaration
TYPES: ty_it_sflight TYPE STANDARD TABLE OF sflight WITH DEFAULT KEY.

* Tabelle anlegen
DATA(it_sflight) = VALUE ty_it_sflight( ( carrid = 'AA' connid = '0123' )
                                        ( carrid = 'LH' connid = '3210' ) ).

* generische Referenz auf die Table
DATA(o_tab) = REF data( it_sflight ).

* Feldsymbol explizit als generische Table definieren
FIELD-SYMBOLS: <tab> TYPE ty_it_sflight. " hier auch STANDARD TABLE möglich
* Feldsymbol auf die interne Tabelle mit der generischen Referenz verknüpfen
ASSIGN o_tab->* TO <tab>.

* wenn Zeilen in der Tabelle vorhanden
IF lines( <tab> ) > 0.
* erste Zeile holen und mit Feldsymbol verknüpfen
  ASSIGN <tab>[ 1 ] TO FIELD-SYMBOL(<row>).
* Struktur (Felder) der ersten Zeile ermitteln
  DATA(o_struct) = CAST cl_abap_structdescr( cl_abap_typedescr=>describe_by_data( <row> ) ).
* Anzahl Felder in der Struktur ermitteln
  DATA(lv_cnt) = lines( o_struct->get_components( ) ).

* Tabelleninhalt darstellen
  LOOP AT <tab> ASSIGNING <row>.

    DATA(lv_row) = ||.

* Felder durchgehen
    DO lv_cnt TIMES.
* Zellen einer zeile holen
      ASSIGN COMPONENT sy-index OF STRUCTURE <row> TO FIELD-SYMBOL(<cell>).
* Zellinhalt ausgeben
      IF sy-index = 0.
        lv_row = |{ <cell> }|.
      ELSE.
        lv_row = |{ lv_row } \| { <cell> }|.
      ENDIF.
    ENDDO.

    WRITE: / lv_row.
  ENDLOOP.

ENDIF.