CDS Table Functions mit AMDP: Komplexe Berechnungen in CDS

kategorie
CDS
Veröffentlicht
autor
Johannes

CDS Table Functions ermöglichen die Integration von komplexer SQLScript-Logik direkt in CDS Views. Mit AMDP (ABAP Managed Database Procedures) kannst du Berechnungen durchführen, die in reinem CDS nicht möglich sind, und dabei die volle Power der HANA-Datenbank nutzen.

Grundkonzept

CDS Table Functions sind eine Brücke zwischen deklarativen CDS Views und imperativem SQLScript:

CDS ViewCDS Table Function
Deklarative LogikImperative SQLScript-Logik
Standard SQL-OperationenNative HANA-Funktionen
Automatische OptimierungManuelle Optimierung möglich
Einfache Joins und FilterKomplexe Algorithmen
Keine VariablenLokale Variablen und Schleifen

Anwendungsfälle

CDS Table Functions sind ideal für:

  • Komplexe Berechnungen: Algorithmen, die in CDS nicht abbildbar sind
  • Window Functions: Moving Averages, Running Totals, Ranking
  • Rekursive Abfragen: Stücklisten-Auflösung, Pfadberechnung
  • String-Operationen: Komplexe Textmanipulationen
  • Datenkonsolidierung: Mehrere Datenquellen zusammenführen
  • Native HANA-Funktionen: Geo-Funktionen, Textsuche, Prediction

CDS Table Function Definition

Die Definition erfolgt in zwei Teilen: CDS-Definition und AMDP-Implementierung.

CDS-Definition

@EndUserText.label: 'Sales Statistics'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_SALES_STATISTICS'
define table function ZTF_SalesStatistics
with parameters
@Environment.systemField: #CLIENT
p_client : abap.clnt,
p_date_from : abap.dats,
p_date_to : abap.dats
returns
{
client : abap.clnt;
product_id : abap.char(10);
product_name : abap.char(40);
total_quantity : abap.quan(13,3);
total_amount : abap.curr(15,2);
avg_price : abap.curr(15,2);
order_count : abap.int4;
first_order : abap.dats;
last_order : abap.dats;
}

Syntax-Elemente

ElementBeschreibung
@ObjectModel.query.implementedByAMDP-Klasse für die Implementierung
with parametersInput-Parameter für die Function
returnsStruktur des Rückgabeergebnisses
@Environment.systemField: #CLIENTAutomatische Mandantenbehandlung

AMDP Implementation Class

Die Implementierung erfolgt in einer AMDP-Klasse:

CLASS zcl_sales_statistics DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_amdp_marker_hdb.
CLASS-METHODS get_statistics
FOR TABLE FUNCTION ztf_salesstatistics.
ENDCLASS.
CLASS zcl_sales_statistics IMPLEMENTATION.
METHOD get_statistics
BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING zsales_order zsales_item zproduct.
RETURN
SELECT
so.client,
si.product_id,
p.product_name,
SUM( si.quantity ) AS total_quantity,
SUM( si.amount ) AS total_amount,
AVG( si.price ) AS avg_price,
COUNT( DISTINCT so.order_id ) AS order_count,
MIN( so.order_date ) AS first_order,
MAX( so.order_date ) AS last_order
FROM zsales_order AS so
INNER JOIN zsales_item AS si
ON so.client = si.client
AND so.order_id = si.order_id
INNER JOIN zproduct AS p
ON si.client = p.client
AND si.product_id = p.product_id
WHERE so.client = :p_client
AND so.order_date BETWEEN :p_date_from AND :p_date_to
GROUP BY so.client, si.product_id, p.product_name;
ENDMETHOD.
ENDCLASS.

AMDP-Syntax Elemente

ElementBeschreibung
if_amdp_marker_hdbMarker-Interface für HANA-Datenbank
FOR TABLE FUNCTIONVerknüpfung mit CDS Table Function
BY DATABASE FUNCTIONKennzeichnung als DB-Funktion
LANGUAGE SQLSCRIPTVerwendete DB-Sprache
OPTIONS READ-ONLYNur lesender Zugriff
USINGVerwendete Datenbankobjekte deklarieren

Input-Parameter verwenden

Verschiedene Parameter-Typen

@EndUserText.label: 'Inventory Analysis'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_INVENTORY_ANALYSIS'
define table function ZTF_InventoryAnalysis
with parameters
@Environment.systemField: #CLIENT
p_client : abap.clnt,
p_plant : abap.char(4),
p_key_date : abap.dats,
@Consumption.defaultValue: '100'
p_threshold : abap.int4
returns
{
client : abap.clnt;
plant : abap.char(4);
material : abap.char(18);
description : abap.char(40);
stock_qty : abap.quan(13,3);
unit : abap.unit(3);
stock_value : abap.curr(15,2);
currency : abap.cuky(5);
below_min : abap.char(1);
}

Parameter in AMDP verwenden

METHOD get_inventory_analysis
BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING zinventory zmaterial.
-- Lokale Variable deklarieren
DECLARE lv_threshold INT;
lv_threshold := :p_threshold;
-- Parameter in WHERE-Klausel
lt_inventory =
SELECT
i.client,
i.plant,
i.material,
m.description,
i.stock_qty,
i.unit,
i.stock_value,
i.currency,
CASE
WHEN i.stock_qty < lv_threshold THEN 'X'
ELSE ''
END AS below_min
FROM zinventory AS i
INNER JOIN zmaterial AS m
ON i.client = m.client
AND i.material = m.material
WHERE i.client = :p_client
AND i.plant = :p_plant
AND i.key_date = :p_key_date;
RETURN :lt_inventory;
ENDMETHOD.

Komplexe SQLScript-Logik

Window Functions

METHOD get_sales_ranking
BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING zsales_data.
RETURN
SELECT
client,
sales_rep,
region,
sales_amount,
-- Ranking innerhalb Region
RANK() OVER (
PARTITION BY region
ORDER BY sales_amount DESC
) AS region_rank,
-- Running Total
SUM( sales_amount ) OVER (
PARTITION BY region
ORDER BY sales_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total,
-- Moving Average (letzte 3 Perioden)
AVG( sales_amount ) OVER (
PARTITION BY sales_rep
ORDER BY sales_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3
FROM zsales_data
WHERE client = :p_client
AND sales_date BETWEEN :p_date_from AND :p_date_to;
ENDMETHOD.

Rekursive Stücklistenauflösung

METHOD get_bom_explosion
BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING zbom.
-- Rekursive CTE für Stücklisten-Explosion
lt_bom_exploded =
WITH RECURSIVE bom_tree AS (
-- Basis: Wurzelmaterial
SELECT
client,
parent_material,
component,
quantity,
unit,
1 AS level,
parent_material AS root_material
FROM zbom
WHERE client = :p_client
AND parent_material = :p_material
UNION ALL
-- Rekursion: Komponenten der Komponenten
SELECT
b.client,
b.parent_material,
b.component,
b.quantity * t.quantity AS quantity,
b.unit,
t.level + 1 AS level,
t.root_material
FROM zbom AS b
INNER JOIN bom_tree AS t
ON b.client = t.client
AND b.parent_material = t.component
WHERE t.level < 10 -- Max. 10 Ebenen
)
SELECT * FROM bom_tree;
RETURN :lt_bom_exploded;
ENDMETHOD.

Mehrere Zwischentabellen

METHOD get_complex_analysis
BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING zorders zcustomers zproducts.
-- Schritt 1: Kundenstatistik
lt_customer_stats =
SELECT
client,
customer_id,
COUNT(*) AS order_count,
SUM( amount ) AS total_amount
FROM zorders
WHERE client = :p_client
AND order_date >= :p_date_from
GROUP BY client, customer_id;
-- Schritt 2: Kundenklassifizierung
lt_customer_class =
SELECT
cs.*,
CASE
WHEN cs.total_amount > 100000 THEN 'A'
WHEN cs.total_amount > 50000 THEN 'B'
WHEN cs.total_amount > 10000 THEN 'C'
ELSE 'D'
END AS customer_class
FROM :lt_customer_stats AS cs;
-- Schritt 3: Mit Stammdaten anreichern
RETURN
SELECT
cc.client,
cc.customer_id,
c.customer_name,
c.city,
c.country,
cc.order_count,
cc.total_amount,
cc.customer_class
FROM :lt_customer_class AS cc
INNER JOIN zcustomers AS c
ON cc.client = c.client
AND cc.customer_id = c.customer_id;
ENDMETHOD.

CDS View mit Table Function

Eine Table Function kann in CDS Views verwendet werden:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Statistics View'
define view entity ZI_SalesStatisticsView
with parameters
p_date_from : abap.dats,
p_date_to : abap.dats
as select from ZTF_SalesStatistics(
p_client : $session.client,
p_date_from : $parameters.p_date_from,
p_date_to : $parameters.p_date_to
)
{
key product_id as ProductID,
product_name as ProductName,
total_quantity as TotalQuantity,
total_amount as TotalAmount,
avg_price as AveragePrice,
order_count as OrderCount,
first_order as FirstOrderDate,
last_order as LastOrderDate
}

Parameterübergabe

SyntaxBeschreibung
$session.clientAktueller Mandant
$session.userAktueller Benutzer
$session.system_dateSystemdatum
$parameters.p_nameView-Parameter weiterreichen
'literal'Feste Werte

Projection und Service

@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
define view entity ZC_SalesStatistics
with parameters
p_date_from : abap.dats,
p_date_to : abap.dats
as projection on ZI_SalesStatisticsView(
p_date_from : $parameters.p_date_from,
p_date_to : $parameters.p_date_to
)
{
@UI.lineItem: [{ position: 10 }]
@UI.selectionField: [{ position: 10 }]
key ProductID,
@UI.lineItem: [{ position: 20 }]
ProductName,
@UI.lineItem: [{ position: 30 }]
@Semantics.quantity.unitOfMeasure: 'UnitOfMeasure'
TotalQuantity,
@UI.lineItem: [{ position: 40 }]
@Semantics.amount.currencyCode: 'Currency'
TotalAmount,
@UI.lineItem: [{ position: 50 }]
OrderCount
}

Performance und Einschränkungen

Performance-Aspekte

AspektEmpfehlung
Große DatenmengenFilter so früh wie möglich anwenden
Komplexe JoinsZwischentabellen verwenden
Window FunctionsPARTITION BY mit Bedacht wählen
RekursionMaximale Tiefe begrenzen

Einschränkungen

EinschränkungWorkaround
Nur lesender ZugriffSchreiboperationen in separater AMDP-Prozedur
Keine dynamischen TabellennamenMehrere Table Functions erstellen
Keine Cursor mit FETCHSet-basierte Operationen verwenden
Kein Debugger wie in ABAPCE_FUNCTION für Logging nutzen

Optimierungs-Tipps

METHOD get_optimized_data
BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING ztable1 ztable2.
-- Tipp 1: Filter früh anwenden
lt_filtered =
SELECT *
FROM ztable1
WHERE client = :p_client
AND status = 'ACTIVE';
-- Tipp 2: Nur benötigte Spalten selektieren
lt_subset =
SELECT client, material, quantity
FROM :lt_filtered;
-- Tipp 3: Index-freundliche Joins
RETURN
SELECT
t1.client,
t1.material,
t1.quantity,
t2.description
FROM :lt_subset AS t1
INNER JOIN ztable2 AS t2
ON t1.client = t2.client -- Mandant zuerst
AND t1.material = t2.material; -- Dann Schlüssel
ENDMETHOD.

Fehlerbehandlung

Exceptions in AMDP

METHOD get_data_with_validation
BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING ztable.
-- Eingabevalidierung
IF :p_date_from > :p_date_to THEN
SIGNAL SQL_ERROR_CODE 10001
SET MESSAGE_TEXT = 'Invalid date range: from > to';
END IF;
-- Normale Verarbeitung
RETURN
SELECT *
FROM ztable
WHERE key_date BETWEEN :p_date_from AND :p_date_to;
ENDMETHOD.

Exception in ABAP abfangen

TRY.
SELECT * FROM ztf_myfunction( p_client = @sy-mandt )
INTO TABLE @DATA(lt_result).
CATCH cx_sy_open_sql_db INTO DATA(lx_db).
" Fehler aus Table Function behandeln
DATA(lv_message) = lx_db->get_text( ).
ENDTRY.

Vergleich: CDS View vs. Table Function

KriteriumCDS ViewCDS Table Function
SyntaxDeklarativ (SQL-like)Imperativ (SQLScript)
KomplexitätEinfache bis mittlereHoch
Window FunctionsEingeschränktVolle Unterstützung
RekursionNur mit HIERARCHYVolle CTE-Unterstützung
Lokale VariablenNicht möglichJa
Mehrere SchritteNicht möglichJa (Zwischentabellen)
WartbarkeitHöherNiedriger
TestbarkeitADT Data PreviewADT + manuelle Tests

Best Practices

EmpfehlungBegründung
CDS View bevorzugenEinfacher wartbar, besser optimierbar
Table Function nur bei BedarfWenn CDS-Syntax nicht ausreicht
USING vollständig deklarierenAlle verwendeten Objekte auflisten
Parameter validierenFrühzeitig Fehler abfangen
Zwischentabellen nutzenÜbersichtlichkeit und Performance
Kommentare in SQLScriptKomplexe Logik dokumentieren
Unit Tests schreibenAMDP separat testbar
READ-ONLY nutzenKeine unbeabsichtigten Änderungen

Zusammenfassung

CDS Table Functions mit AMDP bieten:

  • SQLScript-Power: Komplexe Berechnungen, die in CDS nicht möglich sind
  • HANA-Optimierung: Native Datenbankfunktionen direkt nutzen
  • Flexibilität: Window Functions, Rekursion, lokale Variablen
  • Integration: Nahtlos in CDS Views verwendbar
  • Performance: Logik wird auf Datenbankebene ausgeführt

Setze Table Functions gezielt ein, wenn CDS-Mittel nicht ausreichen - aber bevorzuge CDS Views für einfachere Anforderungen.

Weiterführende Themen