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 View | CDS Table Function |
|---|---|
| Deklarative Logik | Imperative SQLScript-Logik |
| Standard SQL-Operationen | Native HANA-Funktionen |
| Automatische Optimierung | Manuelle Optimierung möglich |
| Einfache Joins und Filter | Komplexe Algorithmen |
| Keine Variablen | Lokale 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
| Element | Beschreibung |
|---|---|
@ObjectModel.query.implementedBy | AMDP-Klasse für die Implementierung |
with parameters | Input-Parameter für die Function |
returns | Struktur des Rückgabeergebnisses |
@Environment.systemField: #CLIENT | Automatische 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
| Element | Beschreibung |
|---|---|
if_amdp_marker_hdb | Marker-Interface für HANA-Datenbank |
FOR TABLE FUNCTION | Verknüpfung mit CDS Table Function |
BY DATABASE FUNCTION | Kennzeichnung als DB-Funktion |
LANGUAGE SQLSCRIPT | Verwendete DB-Sprache |
OPTIONS READ-ONLY | Nur lesender Zugriff |
USING | Verwendete 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
| Syntax | Beschreibung |
|---|---|
$session.client | Aktueller Mandant |
$session.user | Aktueller Benutzer |
$session.system_date | Systemdatum |
$parameters.p_name | View-Parameter weiterreichen |
'literal' | Feste Werte |
Projection und Service
@AccessControl.authorizationCheck: #NOT_REQUIRED@Metadata.allowExtensions: truedefine 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
| Aspekt | Empfehlung |
|---|---|
| Große Datenmengen | Filter so früh wie möglich anwenden |
| Komplexe Joins | Zwischentabellen verwenden |
| Window Functions | PARTITION BY mit Bedacht wählen |
| Rekursion | Maximale Tiefe begrenzen |
Einschränkungen
| Einschränkung | Workaround |
|---|---|
| Nur lesender Zugriff | Schreiboperationen in separater AMDP-Prozedur |
| Keine dynamischen Tabellennamen | Mehrere Table Functions erstellen |
| Keine Cursor mit FETCH | Set-basierte Operationen verwenden |
| Kein Debugger wie in ABAP | CE_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
| Kriterium | CDS View | CDS Table Function |
|---|---|---|
| Syntax | Deklarativ (SQL-like) | Imperativ (SQLScript) |
| Komplexität | Einfache bis mittlere | Hoch |
| Window Functions | Eingeschränkt | Volle Unterstützung |
| Rekursion | Nur mit HIERARCHY | Volle CTE-Unterstützung |
| Lokale Variablen | Nicht möglich | Ja |
| Mehrere Schritte | Nicht möglich | Ja (Zwischentabellen) |
| Wartbarkeit | Höher | Niedriger |
| Testbarkeit | ADT Data Preview | ADT + manuelle Tests |
Best Practices
| Empfehlung | Begründung |
|---|---|
| CDS View bevorzugen | Einfacher wartbar, besser optimierbar |
| Table Function nur bei Bedarf | Wenn CDS-Syntax nicht ausreicht |
| USING vollständig deklarieren | Alle verwendeten Objekte auflisten |
| Parameter validieren | Frühzeitig Fehler abfangen |
| Zwischentabellen nutzen | Übersichtlichkeit und Performance |
| Kommentare in SQLScript | Komplexe Logik dokumentieren |
| Unit Tests schreiben | AMDP separat testbar |
| READ-ONLY nutzen | Keine 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
- CDS Views Grundlagen - Basis-Konzepte von CDS
- CDS Hierarchien - Alternative für rekursive Strukturen
- RAP mit Custom Entities - Alternative für externe Datenquellen