This is a test message to test the length of the message box.
Login
ABAP CDS Datenmodell
Erstellt von Software-Heroes

CDS - Datenmodell

367

In diesem Artikel wollen wir dir das Datenmodell vorstellen, mit dem wir in Zukunft arbeiten werden. Dazu sollst du die Möglichkeiten haben, es ebenfalls zu verwenden.

Werbung


Artikel-Update: Seit Release 7.57 (S/4 HANA 2022) ist DEFINE VIEW als obsolet gekennzeichnet, stattdessen solltest du DEFINE VIEW ENTITY verwenden. Diese können an einigen Stellen zu den Beispielen abweichen. Mehr Informationen zu den neuen Views findest du in diesem Artikel.



Bereits in einem letzten Artikel haben wir erste Elemente aus dem neuen Datenmodell verwendet. In diesem Artikel wollen wir das Datenmodell, die Tabellen und dazugehörigen Interface Views näher vorstellen. Am Ende sollst du ebenfalls die Möglichkeit haben, das Datenmodell anzulegen und Testdaten zu erzeugen.

 

Tabellen

Wir bauen ein kleines Modell mit drei Stammdatentabellen und zwei Bewegungsdatentabellen auf, im Fokus steht hier der Partner mit dem wir Geschäfte machen, die Materialtabelle und die Rechnung die verschickt wird. Das Datenmodell sieht daher wie folgt aus.

 

 

Zusätzlich hat die Rechnung verschiedene Positionen und ein Partner kann auf verschiedene Materialien noch einen Rabatt bekommen. Die Tabellen sind über die Primär- und Sekundärschlüssel verbunden. Dazu legen wir nun die entsprechenden Tabellen über Eclipse an.

 

Partner
@EndUserText.label : 'Partner Data'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zbs_dmo_partner {
  key client       : abap.clnt not null;
  key partner      : abap.char(10) not null;
  name             : abap.char(60);
  street           : abap.char(80);
  city             : abap.char(60);
  country          : land1;
  payment_currency : abap.cuky;
}

 

Material
@EndUserText.label : 'Material Data'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zbs_dmo_material {
  key client     : abap.clnt not null;
  key material   : abap.char(5) not null;
  name           : abap.char(25);
  description    : abap.char(150);
  @Semantics.quantity.unitOfMeasure : 'zbs_dmo_material.stock_unit'
  stock          : abap.quan(10,0);
  stock_unit     : abap.unit(3);
  @Semantics.amount.currencyCode : 'zbs_dmo_material.currency'
  price_per_unit : abap.curr(15,2);
  currency       : abap.cuky;
}

 

Rabatt
@EndUserText.label : 'Discount Data'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zbs_dmo_discount {
  key client   : abap.clnt not null;
  key partner  : abap.char(10) not null;
  key material : abap.char(5) not null;
  discount     : abap.dec(5,2);
}

 

Rechnung
@EndUserText.label : 'Invoice Data'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zbs_dmo_invoice {
  key client   : abap.clnt not null;
  key document : abap.char(8) not null;
  doc_date     : abap.dats;
  doc_time     : abap.tims;
  partner      : abap.char(10);
}

 

Rechnungsposition
@EndUserText.label : 'Invoice Position Data'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zbs_dmo_position {
  key client     : abap.clnt not null;
  key document   : abap.char(8) not null;
  key pos_number : abap.int2 not null;
  material       : abap.char(5);
  @Semantics.quantity.unitOfMeasure : 'zbs_dmo_material.stock_unit'
  quantity       : abap.quan(10,0);
  @Semantics.amount.currencyCode : 'zbs_dmo_position.currency'
  price          : abap.curr(15,2);
  currency       : abap.cuky;
}

 

Hinweis: Wie dir sicherlich aufgefallen ist, verwenden wir auch bei der Anlage von Tabellen Annotationen um die Währungen und Mengenfelder miteinander zu verknüpfen.

 

Core Data Services

Jedes saubere Datenmodell hat mit Interface oder auch Basic Views eine Grundlage, auf der die weiteren Schichten des Modells aufbauen. Ebenfalls haben wir hier noch die Möglichkeit die Feldnamen zu vereinheitlichen und im gesamten Datenmodell eindeutig zu machen. Das Datenmodell sieht wie folgt aus:

 

Die Felder im Datenmodell sind nun eindeutig und vereinfachen so das zusammenführen der Daten in neue Views. Jeder Feldname beschreibt damit eine eindeutige Aufgabe. Die CDS Views sehen nun wie folgt aus.

 

Partner
@AbapCatalog.sqlViewName: 'ZBSIDMOPARTNER'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Interface for Partner'
define view ZBS_I_DmoPartner
  as select from zbs_dmo_partner
{
  key partner          as PartnerNumber,
      name             as PartnerName,
      street           as Street,
      city             as City,
      country          as Country,
      payment_currency as PaymentCurrency
}

 

Material
@AbapCatalog.sqlViewName: 'ZBSIDMOMATERIAL'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Interface for Material'
define view ZBS_I_DmoMaterial
  as select from zbs_dmo_material
{
  key material       as MaterialNumber,
      name           as MaterialName,
      description    as MaterialDescription,
      stock          as Stock,
      stock_unit     as StockUnit,
      price_per_unit as PricePerUnit,
      currency       as Currency
}

 

Rabatt
@AbapCatalog.sqlViewName: 'ZBSIDMODISCOUNT'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Interface for Discount'
define view ZBS_I_DmoDiscount
  as select from zbs_dmo_discount
{
  key partner  as PartnerNumber,
  key material as MaterialNumber,
      discount as DiscountValue
}

 

Rechnung
@AbapCatalog.sqlViewName: 'ZBSIDMOINVOICE'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Interface for Invoice'
define view ZBS_I_DmoInvoice
  as select from zbs_dmo_invoice
{
  key document as DocumentNumber,
      doc_date as DocumentDate,
      doc_time as DocumentTime,
      partner  as PartnerNumber
}

 

Rechnungsposition
@AbapCatalog.sqlViewName: 'ZBSIDMOPOSITION'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Interface for Position'
define view ZBS_I_DmoPosition
  as select from zbs_dmo_position
{
  key document   as DocumentNumber,
  key pos_number as PositionNumber,
      material   as MaterialNumber,
      quantity   as PositionQuantity,
      price      as PositionPrice,
      currency   as PositionCurrency
}

 

Testdaten

Nichts geht über gute Testdaten oder einfach eine gewisse Menge von Testdaten, mit denen du als Entwickler arbeiten kannst. Dazu schreiben wir eine kleine Klasse mit der die Daten einfach generiert werden können. Für die Belege und Positionen kannst du auch noch die Anzahl und die Varianz der Positionen konfigurieren. Das gesamte Beispielcoding findest du hier:

CLASS zcl_bs_demo_dummy_data DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

    CONSTANTS:
      c_error                     TYPE zbs_dmo_position-price VALUE '37707',
      c_number_of_invoices        TYPE i VALUE 300,
      c_days_back_from_today      TYPE i VALUE 365,
      c_max_number_of_positions   TYPE i VALUE 3,
      c_max_quantity_per_position TYPE i VALUE 5.

  PROTECTED SECTION.
  PRIVATE SECTION.
    DATA:
      mt_partner         TYPE STANDARD TABLE OF zbs_dmo_partner,
      mt_material        TYPE STANDARD TABLE OF zbs_dmo_material,
      mt_discount        TYPE STANDARD TABLE OF zbs_dmo_discount,
      mt_head            TYPE STANDARD TABLE OF zbs_dmo_invoice,
      mt_position        TYPE STANDARD TABLE OF zbs_dmo_position,

      mo_random_partner  TYPE REF TO zcl_bs_demo_random,
      mo_random_date     TYPE REF TO zcl_bs_demo_random,
      mo_random_position TYPE REF TO zcl_bs_demo_random,
      mo_random_material TYPE REF TO zcl_bs_demo_random,
      mo_random_quantity TYPE REF TO zcl_bs_demo_random.

    METHODS:
      create_partner,

      create_material,

      create_discount,

      create_invoice
        IMPORTING
          id_count TYPE i,

      create_head
        RETURNING
          VALUE(rs_result) TYPE zbs_dmo_invoice,

      create_positions
        IMPORTING
          is_head TYPE zbs_dmo_invoice.
ENDCLASS.


CLASS zcl_bs_demo_dummy_data IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    create_partner( ).
    out->write( |Partner: { lines( mt_partner ) }| ).

    create_material( ).
    out->write( |Material: { lines( mt_material ) }| ).

    create_discount( ).
    out->write( |Discount: { lines( mt_discount ) }| ).

    create_invoice( c_number_of_invoices ).
    out->write( |Invoice: { lines( mt_head ) }| ).
    out->write( |Position: { lines( mt_position ) }| ).
  ENDMETHOD.


  METHOD create_partner.
    mt_partner = VALUE #(
      ( partner = '1000000000' name = 'SAP' street = 'Demo Street 15' city = 'Walldorf' country = 'DE' payment_currency = 'EUR' )
      ( partner = '1000000001' name = 'Microsoft' street = 'Demo Street 24' city = 'Redmond' country = 'US' payment_currency = 'USD' )
      ( partner = '1000000002' name = 'Meta' street = 'Fox Street 1' city = 'Menlo Park' country = 'US' payment_currency = 'USD' )
      ( partner = '1000000003' name = 'Alibaba' street = 'Alley 15' city = 'Hangzhou' country = 'CN' payment_currency = 'CNY' )
      ( partner = '1000000004' name = 'BMW' street = 'Main Avenue 200' city = 'Munich' country = 'DE' payment_currency = 'EUR' )
      ( partner = '1000000005' name = 'Nestle' street = 'Village Alley 14' city = 'Vevey' country = 'CH' payment_currency = 'CHF' )
      ( partner = '1000000006' name = 'Gazprom' street = 'Peace Avenue 1' city = 'Sankt Petersburg' country = 'RU' payment_currency = 'RUB' )
    ).

    DELETE FROM zbs_dmo_partner.
    INSERT zbs_dmo_partner FROM TABLE @mt_partner.
  ENDMETHOD.


  METHOD create_material.
    mt_material = VALUE #(
      ( material = 'F0001'
        name = 'Peanuts'
        description = 'Roasted Peanuts from US'
        stock = '900'
        stock_unit = 'ST'
        price_per_unit = '2.50'
        currency = 'USD' )
      ( material = 'F0002'
        name = 'Rice'
        description = 'Big bag rice from china'
        stock = '120'
        stock_unit = 'BAG'
        price_per_unit = '12.00'
        currency = 'USD' )
      ( material = 'F0003'
        name = 'Eggs'
        description = 'Eggs from happy german chickens'
        stock = '550'
        stock_unit = 'PAK'
        price_per_unit = '3.15'
        currency = 'EUR' )
      ( material = 'H0001'
        name = 'USB Stick 128 GB'
        description = 'USB Stick with security features'
        stock = '30'
        stock_unit = 'ST'
        price_per_unit = '49.99'
        currency = 'EUR' )
      ( material = 'H0002'
        name = 'OLED Display 34"'
        description = 'Big and wide display with HDMI and dsiplay port'
        stock = '18'
        stock_unit = 'ST'
        price_per_unit = '440.00'
        currency = 'USD' )
      ( material = 'R0001'
        name = 'Gas'
        description = 'Gas from sibiria'
        stock = '50000'
        stock_unit = 'MMQ'
        price_per_unit = '1560.00'
        currency = 'RUB' )
    ).

    DELETE FROM zbs_dmo_material.
    INSERT zbs_dmo_material FROM TABLE @mt_material.
  ENDMETHOD.


  METHOD create_discount.
    mt_discount = VALUE #(
      ( partner = '1000000000' material = 'F0003' discount = '10.00' )
      ( partner = '1000000001' material = 'F0001' discount = '15.00' )
      ( partner = '1000000001' material = 'H0002' discount = '3.50' )
      ( partner = '1000000006' material = 'R0001' discount = '7.50' )
    ).

    DELETE FROM zbs_dmo_discount.
    INSERT zbs_dmo_discount FROM TABLE @mt_discount.
  ENDMETHOD.


  METHOD create_invoice.
    DO id_count TIMES.
      DATA(ls_head) = create_head( ).
      create_positions( ls_head ).
    ENDDO.

    DELETE FROM zbs_dmo_invoice.
    INSERT zbs_dmo_invoice FROM TABLE @mt_head.
    DELETE FROM zbs_dmo_position.
    INSERT zbs_dmo_position FROM TABLE @mt_position.
  ENDMETHOD.


  METHOD create_head.
    DATA:
      ld_document TYPE n LENGTH 8 VALUE 30000000.

    IF mo_random_partner IS INITIAL.
      mo_random_partner = NEW #( id_min = 1 id_max = lines( mt_partner ) ).
      mo_random_date = NEW #( id_min = 1 id_max = c_days_back_from_today ).
    ENDIF.

    rs_result = VALUE #(
      document = ld_document + lines( mt_head )
      doc_date = CONV d( cl_abap_context_info=>get_system_date( ) - mo_random_date->rand( ) )
      doc_time = cl_abap_context_info=>get_system_time( )
      partner = mt_partner[ mo_random_partner->rand( ) ]-partner
    ).

    INSERT rs_result INTO TABLE mt_head.
  ENDMETHOD.


  METHOD create_positions.
    IF mo_random_position IS INITIAL.
      mo_random_position = NEW #( id_min = 1 id_max = c_max_number_of_positions ).
      mo_random_material = NEW #( id_min = 1 id_max = lines( mt_material ) ).
      mo_random_quantity = NEW #( id_min = 1 id_max = c_max_quantity_per_position ).
    ENDIF.

    DO mo_random_position->rand( ) TIMES.
      DATA(ld_index) = sy-index.
      DATA(ls_material) = mt_material[ mo_random_material->rand( ) ].
      DATA(ld_quantity) = mo_random_quantity->rand( ).

      TRY.
          DATA(ld_discount) = mt_discount[ partner = is_head-partner material = ls_material-material ]-discount.
        CATCH cx_sy_itab_line_not_found.
          ld_discount = 0.
      ENDTRY.

      DATA(ls_position) = VALUE zbs_dmo_position(
        document = is_head-document
        pos_number = ld_index
        material = ls_material-material
        quantity = ld_quantity
        price = ( ld_quantity * ls_material-price_per_unit ) * ( 1 - ld_discount / 100 )
        currency = mt_partner[ partner = is_head-partner ]-payment_currency
      ).

      TRY.
          SELECT SINGLE FROM zbs_dmo_discount
            FIELDS
              currency_conversion(
                amount = @ls_position-price,
                source_currency = @ls_material-currency,
                target_currency = @ls_position-currency,
                exchange_rate_date = @is_head-doc_date,
                round = @abap_true
              ) AS price
            INTO @ls_position-price.

        CATCH cx_sy_open_sql_db.
          ls_position-price = c_error.
      ENDTRY.

      INSERT ls_position INTO TABLE mt_position.
    ENDDO.
  ENDMETHOD.
ENDCLASS.

 

Bei der Erstellung setzen wir auf eine Mischung aus festen Stammdaten und zufälligen Positionen, dabei verwenden wir den Zufallsgenerator aus einem anderen Artikel. Damit wird die Anzahl der Positionen, der Partner und der verkauften Einheiten zufällig abgeleitet, sodass wie eine weitreichende Ergebnismenge erhalten. Das Coding hat soweit keine Besonderheiten, bis auf einen Fall:

TRY.
    SELECT SINGLE FROM zbs_dmo_discount
      FIELDS
        currency_conversion(
          amount = @ls_position-price,
          source_currency = @ls_material-currency,
          target_currency = @ls_position-currency,
          exchange_rate_date = @is_head-doc_date,
          round = @abap_true
        ) AS price
      INTO @ls_position-price.

  CATCH cx_sy_open_sql_db.
    ls_position-price = c_error.
ENDTRY.

 

Hier verwenden wir einen "Dummy"-Select, um die Währungsumrechnung ohne Funktionsbaustein durchzuführen und dafür die Standard ABAP-SQL Funktion zu verwenden. Im Fehlerfall dass die Umrechnung nicht funktioniert, wird ein Error-Code in das Feld übernommen.

 

GitHub

Alle Ressourcen zur Core Data Service Serie werden wir dieses Mal zur Einfachheit auf GitHub zur Verfügung stellen. Über den Link findest du unser Repository und kannst das Model in dein System laden. Die Klassen und das Modell sind auch Cloud Ready für den Einsatz in der BTP. Die Klasse für den Zufallsgenerator ist aber in diesem Paket nicht enthalten.

 

Fazit

Für einen anständigen Test benötigt man ein entsprechendes Datenmodell und auch Testdaten. In diesem Artikel wollten wir dir das Modell näher bringen und dir die Möglichkeit zur Verfügung stellen, es zu nutzen.


Enthaltene Themen:
CDSCore Data ServiceDatenmodell
Kommentare (0)



Und weiter ...

Bist du zufrieden mit dem Inhalt des Artikels? Wir posten jeden Freitag neuen Content im Bereich ABAP und unregelmäßig in allen anderen Bereichen. Schaue bei unseren Tools und Apps vorbei, diese stellen wir kostenlos zur Verfügung.


CDS - Migration der Views

Kategorie - ABAP

Du hast noch viele alte Core Data Services in deinem ABAP System? Zeit für die Migration zur neuen Entität.

15.11.2024

CDS - Typen und Enums

Kategorie - ABAP

Was wird im ABAP Dictionary die Datenelemente ablösen und wie kannst du schon heute die Typen für die Core Data Services verwenden? Mehr hier.

05.11.2024

ABAP in Praxis - String Verarbeitung

Kategorie - ABAP

In diesem praktischen Beispiel schauen wir uns die String Verarbeitung zur Ermittlung der CDS Namen in CamelCase an und wie du das mit ABAP umsetzen kannst.

15.10.2024

ABAP - CDS Extraktor

Kategorie - ABAP

Wie funktioniert der CDS Extraktor in ABAP und welche Herausforderungen gibt es bei der Entwicklung damit. In diesem Artikel schauen wir uns ein paar Details an.

20.09.2024

ABAP Tools - Arbeiten mit Eclipse (CDS Templates)

Kategorie - ABAP

Wieder das falsche CDS Template bei der Erstellung ausgewählt? Hier ein kleiner Tipp um nachträglich noch den View in ABAP zu korrigieren.

02.07.2024