CDS Table Functions
A CDS table function is a CDS view whose body is computed by AMDP
(ABAP Managed Database Procedure) running on HANA. It exposes the result of
a SQLScript procedure as if it were a CDS view, so you can use it with
SELECT from ABAP.
Two artifacts are always required:
- DDLS (
*.ddls.asddls) — the signature:define table function, return-row columns, pointer to the implementing AMDP method - AMDP class (
*.clas.abap) —INTERFACES if_amdp_marker_hdband aCLASS-METHODS x FOR TABLE FUNCTION <ddls>method implementedBY DATABASE FUNCTION FOR HDB LANGUAGE SQLSCRIPT
→ For the exact XML metadata files (SOURCE_TYPE F, CLSCCINCL X) and
boilerplate .asddls / .clas.abap source: abapgit-agent ref --topic abapgit-cds,
section “CDS Table Function (DDLS, SOURCE_TYPE F)”.
When to use a table function vs. a view entity
| Need | Use |
|---|---|
| Pure CDS DDL is enough (joins, aggregations, conditions) | View entity (define view entity, SOURCE_TYPE W) |
| SQLScript constructs (window functions, recursive CTE, MERGE-like joins, dynamic SQL) | Table function |
Access a table in a different / replicated HANA schema via $ABAP.schema( <logical> ) |
Table function (CDS view entities don’t support $ABAP.schema(...)) |
Logical schema — accessing replicated / foreign schemas
Table functions can read from replicated HANA schemas or schemas of
co-deployed ABAP applications via the $ABAP.schema( ... ) macro:
SELECT ...
FROM "$ABAP.schema( FRA_CORE_ERP )".rbkp
This is the only way to do cross-schema reads from ABAP — CDS view
entities and plain ABAP SQL don’t support the macro. The mapping from logical
to physical schema is maintained per landscape in transaction DB_SCHEMA_MAP.
→ Full guide (setup, syntax, common errors, end-to-end example):
abapgit-agent ref --topic abap-schema-mapping
Return-column types — what to use
| Type expression | Use it for |
|---|---|
abap.clnt |
client / MANDT |
abap.char(N) |
fixed CHAR (e.g. BUKRS → abap.char(4), LIFNR → abap.char(10)) |
abap.numc(N) |
numeric character (e.g. GJAHR → abap.numc(4)) |
abap.dats |
date YYYYMMDD |
abap.tims |
time HHMMSS |
abap.dec(N,M) |
decimal (e.g. invoice amount → abap.dec(23,2)) |
abap.cuky |
currency key (5-char) |
abap.int4 |
4-byte integer |
abap.string |
unbounded NVARCHAR |
mandt, bukrs, lifnr, … |
DDIC data elements — only if active in the same package as the DDLS |
⚠️ Data elements from replicated tables are NOT in the local DDIC
abapgit-agent view --objects WERTV8 --type DTEL may say the data element
exists somewhere in the system, but the DDLS activator only resolves data
elements that are either (a) standard SAP_BASIS types or (b) active in the
same ABAP package as your DDLS.
Symptom:
Entity Z_TF_MY_FUNCTION: Data element WERTV8 parameter/field AMOUNT does not exist or not active
Fix — use a primitive abap.* type:
amount : abap.dec(23,2); // instead of: amount : wertv8;
vendor_reference : abap.char(16); // instead of: xblnr1
Or — if you want strong typing and reuse — create your own data elements
under your project namespace and reference them. (This is what production
table function packages typically do: e.g. FRA_ERP_VENDOR,
FRA_ERP_DOCUMENT_DATE.)
Activation order — DDLS depends on the AMDP class
The DDLS contains implemented by method ZCL_X=>METHOD, so the DDLS cannot
activate until the AMDP class is active. The AMDP class declares
CLASS-METHODS m FOR TABLE FUNCTION z_tf_x, but the class compiles against
the signature of the DDLS — which exists from the first pull onwards.
Recommended sequence:
git add . && git commit -m "..." && git push
abapgit-agent pull --files src/zcl_tf_x.clas.abap # class first
abapgit-agent pull --files src/z_tf_x.ddls.asddls # then DDLS
A single pull --files src/zcl_tf_x.clas.abap,src/z_tf_x.ddls.asddls often
works on the second attempt — first pass activates the class, second pass
activates the DDLS. Don’t be alarmed by “Activation cancelled” on attempt 1
if a follow-up pull reports 1 activated.
inspect is NOT reliable for table-function DDLS
abapgit-agent inspect --files src/z_tf_x.ddls.asddls
✅ DDLS Z_TF_X - Syntax check passed # ← but activation FAILS
inspect runs a syntactic check on the DDLS source, not a full DDIC
activation. Data-element-not-found and $ABAP.schema resolution errors
slip through.
To see the real activation error, use one of these:
- Recommended — read the pull log in JSON:
abapgit-agent pull --files src/z_tf_x.ddls.asddls --jsonLook at the
LOG_MESSAGESarray. -
Activate manually in ADT (right-click → activate). The error popup is the most detailed.
- SE80 → DDLS → activate log.
Common errors and fixes
| Error message | Cause | Fix |
|---|---|---|
Data element X parameter/field Y does not exist or not active |
DTEL X is not in the local DDIC | Replace with abap.<primitive>(...) (see type table above) |
'Z_TF_X' is not a table function (from a runner / SELECT) |
The DDLS is inactive | Pull the AMDP class first, then the DDLS |
Annotation for AbapCatalog.dataMaintenance does not exist |
Wrong SOURCE_TYPE in the DDLS XML |
Set <SOURCE_TYPE>F</SOURCE_TYPE> |
Compilation was canceled for the AMDP class |
Class compiles against a DDLS signature that hasn’t been deserialized yet | Re-pull — the second attempt picks up the now-deserialized DDLS |
Error updating where-used list after pulling the AMDP class |
The DDLS the class refers to is missing or inactive | Make sure the .ddls.asddls file is in the same commit and re-pull both |
Class shows create private on the server after pull |
The source line class zcl_x implementation. uses lowercase name — abapGit can’t match it to the serializer output |
Write the implementation header in UPPERCASE: CLASS ZCL_X IMPLEMENTATION. |
Pull --sync-xml wipes the DDLS XML to a minimal stub |
--sync-xml was run after a pull that failed to deserialize the DDLS; the serializer output is empty, and you overwrote your good XML |
Never use --sync-xml after a failed pull. Restore the XML from git history first, fix the underlying activation error, re-pull, then --sync-xml |
Example — duplicate vendor invoice detector
A complete two-file table function over a replicated RBKP table:
src/z_tf_duplicate_invoices.ddls.asddls:
@EndUserText.label: 'Duplicate Vendor Invoices'
@ClientHandling.type: #CLIENT_DEPENDENT
define table function Z_TF_DUPLICATE_INVOICES
returns {
client : abap.clnt;
company_code : abap.char(4);
document_number : abap.char(10);
fiscal_year : abap.numc(4);
vendor : abap.char(10);
vendor_reference : abap.char(16);
amount : abap.dec(23,2);
currency : abap.cuky;
dup_count : abap.int4;
}
implemented by method ZCL_TF_DUPLICATE_INVOICES=>GET_DUPLICATES;
src/zcl_tf_duplicate_invoices.clas.abap (excerpt):
METHOD get_duplicates BY DATABASE FUNCTION FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY.
RETURN SELECT
r.mandt AS client,
r.bukrs AS company_code,
r.belnr AS document_number,
r.gjahr AS fiscal_year,
r.lifnr AS vendor,
r.xblnr AS vendor_reference,
r.rmwwr AS amount,
r.waers AS currency,
CAST( d.cnt AS INTEGER ) AS dup_count
FROM "$ABAP.schema( FRA_CORE_ERP )".rbkp AS r INNER JOIN
( SELECT mandt, lifnr, xblnr, rmwwr, waers,
COUNT(*) AS cnt
FROM "$ABAP.schema( FRA_CORE_ERP )".rbkp
WHERE xblnr <> '' AND mandt = SESSION_CONTEXT( 'CLIENT' )
GROUP BY mandt, lifnr, xblnr, rmwwr, waers
HAVING COUNT(*) > 1 ) AS d
ON r.mandt = d.mandt AND
r.lifnr = d.lifnr AND
r.xblnr = d.xblnr AND
r.rmwwr = d.rmwwr AND
r.waers = d.waers
WHERE r.mandt = SESSION_CONTEXT( 'CLIENT' );
ENDMETHOD.
Call it from regular ABAP:
SELECT vendor, vendor_reference, amount, currency, dup_count
FROM z_tf_duplicate_invoices( )
INTO TABLE @DATA(lt_dups)
UP TO 100 ROWS.
Workflow recap
Create/modify table function?
└── ✅ Use:
edit .ddls.asddls + .clas.abap (no syntax — inspect is unreliable for TFs)
→ [abaplint]
→ commit → push
→ pull --files <clas>.clas.abap (activates AMDP)
→ pull --files <ddls>.ddls.asddls --sync-xml (activates DDLS)
→ (verify) abapgit-agent run --class <runner_class>