CDS - Assoziation
In diesem Artikel geht es um Assoziationen, die eine ähnliche Funktionalität wie Joins zur Verfügung stellen. Wir werden dir aber zeigen, was die Unterschiede sind.
Inhaltsverzeichnis
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.
Im letzten Artikel haben wir die Joins und den Union näher gebracht und wie du damit Daten in Core Data Services verknüpfen kannst. Diese neuen Views kannst du dann immer wieder verwenden und musst die Logik nur einmal implementieren. Neben den Joins gibt es aber noch ein weiteres Werkzeug abhängige Daten im Datenmodell zur Verfügung zu stellen.
Assoziation
Eine Assoziation erstellt eine Beziehung zu abhängigen Daten und definiert eine Verbindung in den Feldern des CDS Views, die als Exponierung bekannt ist. Ohne die Angabe in der Projektsliste, kann nicht auf die Daten zugegriffen werden. Definieren wir dazu eine Assoziation in unserem Interface View "ZBS_I_DmoInvoice". In diesem View gibt es zwei mögliche Verbindungen, einmal zum Partner und einmal zu den Positionen. Im folgenden Beispiel erzeugen wir eine Assoziation zum Partner:
define view ZBS_I_DmoInvoice
as select from zbs_dmo_invoice as Invoice
association [0..1] to ZBS_I_DmoPartner as _Partner on $projection.PartnerNumber = _Partner.PartnerNumber
{
key document as DocumentNumber,
doc_date as DocumentDate,
doc_time as DocumentTime,
partner as PartnerNumber,
_Partner
}
Die Assoziation wird nach dem SELECT definiert und erhält wie beim Join einen Alias und eine On-Bedingung, um die Daten mit der Zielentität zu verknüpfen. Zusätzlich wird die Assoziation in der Projektionsliste der Felder exponiert, in dem wir den Namen der Assoziation angeben. Als Best Practice beginnen Assoziationen mit Unterstrich, um die Daten und Felder vom Core Data Service abzugrenzen. Hinter der Assoziation wird die Kardinalität der Verbindung hinterlegt, dazu in einem späteren Abschnitt noch mehr. Wie kannst du nun auf diese Daten in einem Select zugreifen, dazu einmal das folgende Beispiel:
SELECT FROM ZBS_I_DmoInvoice
FIELDS DocumentNumber, PartnerNumber, \_Partner-PartnerName, \_Partner-City
INTO TABLE @DATA(lt_association)
UP TO 10 ROWS.
Beim Select kannst du nun über die definierte Assoziation auf die Felder zugreifen und in den Select die entsprechenden Daten einmischen, ohne einen Join definieren zu müssen. Vor dem Unterstrich musst du allerdings mit einem Backslash arbeiten. Das Ergebnis der Abfrage sieht nun wie folgt aus:
Kardinalität
Die definierte Kardinalität in der Assoziation gibt vor allem dem Datenbankoptimizer eine Indikation wie beim Zugriff auf die Daten verfahren werden soll und welcher Zugriff am Optimalsten ist. Gleichzeitig bestimmt die Kardinalität die Ergebnismenge. Folgende Karinalitäten sind möglich:
- [1] - Anzahl 0 bis 1
- [0..1] - Anzahl 0 bis 1
- [1..1] - Anzahl genau 1
- [0..*] - Anzahl 0 bis Unbegrenzt
- [1..*] - Anzahl 1 bis Unbegrenzt
- keine Angabe - Anzahl 0 bis 1
Performance
Du fragst dich sicherlich, was dir die Assoziation nun eigentlich bringt? Im ersten Moment verhalten sie sich wie Joins, doch das Geheimnis passiert beim "Nicht-Zugriff", denn dann kostet es die Datenbank keine Performance. Im Gegensatz zu einem Join bei dem der Zugriff und die Datenverknüpfung jedes Mal passiert, wird die Assoziation nur ausgelöst, wenn Felder auch angesprochen werden. Schauen wir uns dazu das SQL Create Statement an, dieses erreichst du über Rechts-Klick auf den Quellcode und Auswahl von "Show SQL Create Statement":
Im nächsten Schritt definieren wir die Felder in der aktuellen Feldliste und erweitern diese um Felder aus der definierten Assoziation:
define view ZBS_I_DmoInvoice
as select from zbs_dmo_invoice as Invoice
association [0..1] to ZBS_I_DmoPartner as _Partner on $projection.PartnerNumber = _Partner.PartnerNumber
{
key document as DocumentNumber,
doc_date as DocumentDate,
doc_time as DocumentTime,
partner as PartnerNumber,
_Partner.PartnerName,
_Partner.City
}
Die Felder sind nun dauerhaft in den Views eingebunden, das ensprechende Create Statement sieht nun dauerhaft so aus. Das Gleiche passiert auch beim Zugriff auf die Daten, ob mit oder ohne die entsprechenden Felder der Assoziation:
Einbindung
Nun statten wir alle Interface Views des Datenmodells mit Assoziationen zu den verknüpften Daten aus, um die Navigation im Datenmodell zu ermöglichen und leicht weitere Daten zur Verfügung zu stellen.
Rechnung
define view ZBS_I_DmoInvoice
as select from zbs_dmo_invoice as Invoice
association [0..*] to ZBS_I_DmoPosition as _Position on $projection.DocumentNumber = _Position.DocumentNumber
association [0..1] to ZBS_I_DmoPartner as _Partner on $projection.PartnerNumber = _Partner.PartnerNumber
{
key document as DocumentNumber,
doc_date as DocumentDate,
doc_time as DocumentTime,
partner as PartnerNumber,
_Position,
_Partner
}
Rechnungsposition
define view ZBS_I_DmoPosition
as select from zbs_dmo_position
association [0..1] to ZBS_I_DmoInvoice as _Invoice on $projection.DocumentNumber = _Invoice.DocumentNumber
association [0..1] to ZBS_I_DmoMaterial as _Material on $projection.MaterialNumber = _Material.MaterialNumber
{
key document as DocumentNumber,
key pos_number as PositionNumber,
material as MaterialNumber,
quantity as PositionQuantity,
price as PositionPrice,
currency as PositionCurrency,
_Invoice,
_Material
}
Partner
define view ZBS_I_DmoPartner
as select from zbs_dmo_partner
association [0..1] to I_Country as _Country on $projection.Country = _Country.Country
association [0..1] to I_Currency as _Currency on $projection.PaymentCurrency = _Currency.Currency
{
key partner as PartnerNumber,
name as PartnerName,
street as Street,
city as City,
country as Country,
payment_currency as PaymentCurrency,
_Country,
_Currency
}
Material
define view ZBS_I_DmoMaterial
as select from zbs_dmo_material
association [0..1] to I_Currency as _Currency on $projection.Currency = _Currency.Currency
association [0..1] to I_UnitOfMeasure as _Unit on $projection.StockUnit = _Unit.UnitOfMeasure
{
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,
_Currency,
_Unit
}
Rabatt
define view ZBS_I_DmoDiscount
as select from zbs_dmo_discount
association [0..1] to ZBS_I_DmoPartner as _Partner on $projection.PartnerNumber = _Partner.PartnerNumber
association [0..1] to ZBS_I_DmoMaterial as _Material on $projection.MaterialNumber = _Material.MaterialNumber
{
key partner as PartnerNumber,
key material as MaterialNumber,
discount as DiscountValue,
_Partner,
_Material
}
Data-Preview
Die Assoziationen helfen bei der Navigation durch die Daten und die Bereitstellung von Zusatzinformationen. Das können wir dir ganz gut am Data-Preview von Eclipse zeigen. Dazu rufen wir den Data-Preview vom CDS View "ZBS_I_DmoPosition" auf und rufen das Kontext.-Menü zu einem Datensatz auf.
Über "Follow Association" bekommen wir einen Vorschlag zu allen definierten Assoziationen für die weitere Navigation:
So können wir von den Positionen, über die Kopfdaten und den zugeordneten Partner bis zur Währung navigieren und die entsprechenden Daten erhalten.
Kompletter Beleg
Im letzten Artikel hatten wir den CDS View "ZBS_I_DmoCompleteDocument" definiert und mit Joins die Daten zusammengefügt die wir benötigten. Mit den Assoziationen haben wir nun die Möglichkeit das gleiche ohne zusätzliches Objekt oder Join zu tun. Dazu können wir über die Feldliste des SELECTs und die einzelnen Annotationen navigieren. Der Select könnte nun wie folgt aussehen:
SELECT FROM ZBS_I_DmoPosition
FIELDS DocumentNumber,
PositionNumber,
\_Invoice-PartnerNumber,
\_Invoice\_Partner-PartnerName,
\_Invoice\_Partner-City,
\_Invoice\_Partner-Country,
MaterialNumber,
PositionQuantity,
PositionPrice,
PositionCurrency,
\_Invoice-DocumentDate
INTO TABLE @DATA(lt_full_with_association)
UP TO 20 ROWS.
Fazit
Die Assoziation ergänzt das gesamte Datenmodell um Beziehungen und Kontext-Informationen ohne die Performance zu verschlechtern. Damit solltest du alle deine Interface Views mit entsprechenden Daten versorgen, um diese später bei Zugriffen nutzen zu können, ohne das Datenmodell erweitern zu müssen.