CO-PA transfer structure (KEI1) in HANA SQL and ABAP SQL: TKB9C/TKB9G/TKB9F, plus cost element group expansion

The PA transfer structure (KEI1) — its assignment lines, the source cost elements and the value field assignments — read straight from the tables. HANA SQL for DB02/ST04 → SQL Editor.

Sections 1–2 were verified on the EWM system (client 700, transfer structure FI, controlling area CA10) on 2026-08-28. Sections 3–5 were added on 2026-08-31 and verified on an S/4HANA sandbox (client 100, transfer structure FI, controlling area 0001).

1. Assignment lines (TKB9C × TKB9D)

CaseAssignment lines of a transfer structure (TKB9C × TKB9D)
What it doesTKB9C holds the assignment lines (ERZUO), TKB9D the line texts. FAKMG = 'X' corresponds to the Qty billed/delivered checkbox in KEI1. The output is the left-hand list of the KEI1 screen.
Watch outThe transfer structure header is TKB9A and its text is TKB9B. All TKB9* tables are client-dependent (they have MANDT).
Result3 rows — 010 Salary, 020 Depreciation, 030 IO Salary (same as the KEI1 screen)
SELECT c.ERSCH, c.ERZUO, d.ZTEXT, c.FAKMG
  FROM TKB9C c
  LEFT JOIN TKB9D d
     ON d.MANDT = c.MANDT
    AND d.ERSCH = c.ERSCH
    AND d.ERZUO = c.ERZUO
    AND d.SPRAS = 'E'
 WHERE c.MANDT = '700'
   AND c.ERSCH = 'FI'
 ORDER BY c.ERZUO;

2. Source and value field per line (TKB9G × TKB9F)

CaseSource (cost element) + value field assignment (TKB9G × TKB9F)
What it doesTKB9G is the source: per controlling area (KOKRS) either a cost element group (SETNR) or a single value / range (VALMIN~VALMAX). TKB9F is the value field assignment: value field (WRTFLD) per operating concern (ERKRS). One line can feed a different value field in each operating concern.
Watch outFVKZ is the fixed/variable indicator (1 = fixed, 3 = total). Without the KOKRS condition the standard sample controlling areas (0001, CA01, DE01, ...) show up as well.
Result5 rows — 010: cost element 5070 → OC10/VV030 and S001/VWGK; 020: 5080 → VV040 and VRPRS; 030: 8040 → VV030 (FVKZ = 1)
SELECT c.ERZUO, d.ZTEXT, g.KOKRS, g.SETNR, g.VALMIN, g.VALMAX,
       f.ERKRS, f.FVKZ, f.WRTFLD
  FROM TKB9C c
  LEFT JOIN TKB9D d
     ON d.MANDT = c.MANDT AND d.ERSCH = c.ERSCH
    AND d.ERZUO = c.ERZUO AND d.SPRAS = 'E'
  LEFT JOIN TKB9G g
     ON g.MANDT = c.MANDT AND g.ERSCH = c.ERSCH
    AND g.ERZUO = c.ERZUO AND g.KOKRS = 'CA10'
  LEFT JOIN TKB9F f
     ON f.MANDT = c.MANDT AND f.ERSCH = c.ERSCH
    AND f.ERZUO = c.ERZUO
 WHERE c.MANDT = '700'
   AND c.ERSCH = 'FI'
 ORDER BY c.ERZUO, f.ERKRS;

3. ABAP SQL, fully expanded (one row = transfer structure × assignment × source × value field)

CaseList of transfer structures (TKB9A × TKB9B)
What it doesTKB9A is the transfer structure header, TKB9B the language-dependent name. Same as the first-screen list of KEI1/KEI2.
Watch outTKB9A has practically no fields besides ERSCH (only check flags). The name only appears when TKB9B is joined.
Result9 rows — A1 / BK / BP / CO / E1 / F1 / FI / K1 / Y1 (S/4HANA, measured 2026-08-31)
SELECT a~ersch, b~stext
  FROM tkb9a AS a
  LEFT OUTER JOIN tkb9b AS b
    ON b~ersch = a~ersch
   AND b~spras = 'E'
  ORDER BY a~ersch
  INTO TABLE @DATA(lt_str).
CaseFull expansion + text joins for the code values
What it doesOne row = transfer structure × assignment × sending cost element (source) × value field. Joining TKB9G (source, KOKRS axis) and TKB9F (value field, ERKRS axis) together gives the cross product of the two axes. MWKZ (1 = value field / 2 = quantity field), FVKZ (1 = fixed / 2 = variable / 3 = total) and ABKAT (variance category) are resolved to domain texts through DD07T.
Watch outThe SUBCLASS of the cost element group name (SETHEADERT) is the chart of accounts (KTOPL), not the controlling area. Join via TKA01-KTOPL to get exactly one row (KOKRS 0001 → KTOPL INT). Without SUBCLASS the row count multiplies by the number of charts of accounts.
Watch outValue field texts cannot be derived by a naming rule, because each value field has its own data element (VV500 → RKE2_VV500, KWABPR → RKESKWABPR). The query goes through the field definition of CE1<ERKRS> in DD03L and then reads DD04T.
Watch outIn the ADT Data Preview (SQL console) the request fails with HTTP 400 once the number of joins grows, so the whole query could not be run there. It was verified in pieces of 2–3 joins and then combined. Run it from SE16N, an ABAP report, or DB02 SQL.
Result13 rows — transfer structure FI / controlling area 0001 (S/4HANA, measured 2026-08-31)
SELECT c~ersch      AS transfer_str,   bt~stext  AS str_text,
       c~erzuo      AS assignment,     dt~ztext  AS asg_text,
       g~kokrs      AS co_area,        ka~bezei  AS co_area_text,
       g~class      AS set_class,      g~setnr   AS ce_group,
       sh~descript  AS ce_group_text,
       g~valmin     AS ce_from,        g~valmax  AS ce_to,
       g~abkat      AS var_categ,      ab~ddtext AS var_categ_text,
       f~erkrs      AS op_concern,
       f~wrtfld     AS value_field,    d4~ddtext AS value_field_text,
       f~mwkz       AS qty_val_ind,    mw~ddtext AS qty_val_text,
       f~fvkz       AS fix_var_ind,    fv~ddtext AS fix_var_text
  FROM tkb9c AS c
  LEFT OUTER JOIN tkb9b AS bt ON bt~ersch = c~ersch AND bt~spras = 'E'
  LEFT OUTER JOIN tkb9d AS dt ON dt~ersch = c~ersch AND dt~erzuo = c~erzuo
                             AND dt~spras = 'E'
  LEFT OUTER JOIN tkb9g AS g  ON g~ersch  = c~ersch AND g~erzuo  = c~erzuo
  LEFT OUTER JOIN tka01 AS ka ON ka~kokrs = g~kokrs
  LEFT OUTER JOIN setheadert AS sh ON sh~setclass = g~class
                                  AND sh~subclass = ka~ktopl
                                  AND sh~setname  = g~setnr
                                  AND sh~langu    = 'E'
  LEFT OUTER JOIN dd07t AS ab ON ab~domname    = 'ABKAT'
                             AND ab~domvalue_l = g~abkat
                             AND ab~ddlanguage = 'E'
  LEFT OUTER JOIN tkb9f AS f  ON f~ersch = c~ersch AND f~erzuo = c~erzuo
  LEFT OUTER JOIN dd03l AS l  ON l~tabname   = concat( 'CE1', f~erkrs )
                             AND l~fieldname = f~wrtfld
  LEFT OUTER JOIN dd04t AS d4 ON d4~rollname   = l~rollname
                             AND d4~ddlanguage = 'E'
  LEFT OUTER JOIN dd07t AS mw ON mw~domname    = 'RKE_MWKZ2'
                             AND mw~domvalue_l = f~mwkz
                             AND mw~ddlanguage = 'E'
  LEFT OUTER JOIN dd07t AS fv ON fv~domname    = 'RKE_FVKZ'
                             AND fv~domvalue_l = f~fvkz
                             AND fv~ddlanguage = 'E'
 WHERE c~ersch = 'FI'
   AND g~kokrs = '0001'
 ORDER BY c~ersch, c~erzuo, g~kokrs, f~erkrs, f~wrtfld
  INTO TABLE @DATA(lt_out).

4. DB02/ST04 HANA SQL, fully expanded (HANA SQL version of section 3)

CaseFull expansion in HANA SQL (DB02/ST04 SQL Editor)
What it doesReturns the same result as the ABAP SQL in section 3. The join structure is identical; only the notation differs. MANDT goes into the conditions and joins explicitly, ABAP SQL concat() becomes CONCAT(), and the DDIC text tables get AS4LOCAL = 'A' (active version).
Watch outThe CLASS column of TKB9G collides with a HANA reserved word and must be written as g."CLASS" in double quotes.
Watch outDD03L / DD04T / DD07T are client-independent — do not add a MANDT join on them.
Watch outSETHEADERT SUBCLASS is the chart of accounts (KTOPL). Join via TKA01-KTOPL to get one row.
Result13 rows — identical to the section 3 ABAP SQL result down to the values. Confirmed by running this SQL unchanged through ADBC (CL_SQL_STATEMENT) on S/4HANA client 100 (2026-08-31).
SELECT c.ERSCH, bt.STEXT AS STR_TEXT,
       c.ERZUO, dt.ZTEXT AS ASG_TEXT,
       g.KOKRS, ka.BEZEI AS CO_AREA_TEXT,
       g."CLASS", g.SETNR, sh.DESCRIPT AS CE_GROUP_TEXT,
       g.VALMIN, g.VALMAX,
       g.ABKAT, ab.DDTEXT AS VAR_CATEG_TEXT,
       f.ERKRS, f.WRTFLD, d4.DDTEXT AS VALUE_FIELD_TEXT,
       f.MWKZ, mw.DDTEXT AS QTY_VAL_TEXT,
       f.FVKZ, fv.DDTEXT AS FIX_VAR_TEXT
  FROM TKB9C c
  LEFT JOIN TKB9B bt ON bt.MANDT=c.MANDT AND bt.ERSCH=c.ERSCH AND bt.SPRAS='E'
  LEFT JOIN TKB9D dt ON dt.MANDT=c.MANDT AND dt.ERSCH=c.ERSCH
                    AND dt.ERZUO=c.ERZUO AND dt.SPRAS='E'
  LEFT JOIN TKB9G g  ON g.MANDT=c.MANDT AND g.ERSCH=c.ERSCH AND g.ERZUO=c.ERZUO
  LEFT JOIN TKA01 ka ON ka.MANDT=c.MANDT AND ka.KOKRS=g.KOKRS
  LEFT JOIN SETHEADERT sh ON sh.MANDT=c.MANDT AND sh.SETCLASS=g."CLASS"
                         AND sh.SUBCLASS=ka.KTOPL AND sh.SETNAME=g.SETNR
                         AND sh.LANGU='E'
  LEFT JOIN DD07T ab ON ab.DOMNAME='ABKAT' AND ab.DOMVALUE_L=g.ABKAT
                    AND ab.DDLANGUAGE='E' AND ab.AS4LOCAL='A'
  LEFT JOIN TKB9F f  ON f.MANDT=c.MANDT AND f.ERSCH=c.ERSCH AND f.ERZUO=c.ERZUO
  LEFT JOIN DD03L l  ON l.TABNAME=CONCAT('CE1', f.ERKRS) AND l.FIELDNAME=f.WRTFLD
                    AND l.AS4LOCAL='A'
  LEFT JOIN DD04T d4 ON d4.ROLLNAME=l.ROLLNAME AND d4.DDLANGUAGE='E'
                    AND d4.AS4LOCAL='A'
  LEFT JOIN DD07T mw ON mw.DOMNAME='RKE_MWKZ2' AND mw.DOMVALUE_L=f.MWKZ
                    AND mw.DDLANGUAGE='E' AND mw.AS4LOCAL='A'
  LEFT JOIN DD07T fv ON fv.DOMNAME='RKE_FVKZ' AND fv.DOMVALUE_L=f.FVKZ
                    AND fv.DDLANGUAGE='E' AND fv.AS4LOCAL='A'
 WHERE c.MANDT = '100'
   AND c.ERSCH = 'FI'
   AND g.KOKRS = '0001'
 ORDER BY c.ERZUO, f.ERKRS, f.WRTFLD;

5. When the source is a cost element group — expand to individual cost elements

CaseGroup → individual cost elements (SETNODE / SETLEAF / CSKB / SKAT)
What it doesSETNR in TKB9G only carries the group name, so the accounts actually covered are not visible. The group hierarchy is in SETNODE (child groups), the values in SETLEAF (VALFROM~VALTO ranges). Joining the ranges to CSKB (cost element master) keeps only cost elements that really exist, and SKAT adds the account name.
Watch outHANA does not support WITH RECURSIVE (the run fails with "incorrect syntax near "grp""). The hierarchy is therefore unrolled level by level with UNION ALL; this query covers 3 levels (group → child → grandchild). For deeper groups add another UNION ALL block in the same pattern.
Watch outLevel 0 (the group itself) must also be part of the UNION ALL — there is no guarantee that leaves only sit at the lowest level.
Watch outSUBCLASS in SETNODE / SETLEAF is the chart of accounts (KTOPL) as well; it is taken from TKA01-KTOPL.
Result169 rows — assignment 010 = 20 / 020 = 14 / 030 = 75 / 040 = 27 / 050 = 33 (S/4HANA client 100, run unchanged through ADBC, 2026-08-31)
WITH sets (ersch, erzuo, kokrs, root_set, sc, sub, sn) AS (
  -- level 0 : the group itself
  SELECT g.ERSCH, g.ERZUO, g.KOKRS, g.SETNR, g."CLASS", ka.KTOPL, g.SETNR
    FROM TKB9G g
    INNER JOIN TKA01 ka ON ka.MANDT=g.MANDT AND ka.KOKRS=g.KOKRS
   WHERE g.MANDT='100' AND g.ERSCH='FI' AND g.KOKRS='0001' AND g.SETNR<>''
  UNION ALL
  -- level 1 : child groups
  SELECT g.ERSCH, g.ERZUO, g.KOKRS, g.SETNR, n1.SUBSETCLS, n1.SUBSETSCLS, n1.SUBSETNAME
    FROM TKB9G g
    INNER JOIN TKA01 ka ON ka.MANDT=g.MANDT AND ka.KOKRS=g.KOKRS
    INNER JOIN SETNODE n1 ON n1.MANDT=g.MANDT AND n1.SETCLASS=g."CLASS"
                         AND n1.SUBCLASS=ka.KTOPL AND n1.SETNAME=g.SETNR
   WHERE g.MANDT='100' AND g.ERSCH='FI' AND g.KOKRS='0001' AND g.SETNR<>''
  UNION ALL
  -- level 2 : grandchild groups
  SELECT g.ERSCH, g.ERZUO, g.KOKRS, g.SETNR, n2.SUBSETCLS, n2.SUBSETSCLS, n2.SUBSETNAME
    FROM TKB9G g
    INNER JOIN TKA01 ka ON ka.MANDT=g.MANDT AND ka.KOKRS=g.KOKRS
    INNER JOIN SETNODE n1 ON n1.MANDT=g.MANDT AND n1.SETCLASS=g."CLASS"
                         AND n1.SUBCLASS=ka.KTOPL AND n1.SETNAME=g.SETNR
    INNER JOIN SETNODE n2 ON n2.MANDT=g.MANDT AND n2.SETCLASS=n1.SUBSETCLS
                         AND n2.SUBCLASS=n1.SUBSETSCLS AND n2.SETNAME=n1.SUBSETNAME
   WHERE g.MANDT='100' AND g.ERSCH='FI' AND g.KOKRS='0001' AND g.SETNR<>''
)
SELECT s.ersch, s.erzuo, dt.ZTEXT AS ASG_TEXT, s.kokrs,
       s.root_set, sh.DESCRIPT AS GROUP_TEXT, s.sn AS SUB_GROUP,
       lf.VALFROM, lf.VALTO, ce.KSTAR, sk.TXT20 AS ACCOUNT_TEXT
  FROM sets s
  INNER JOIN SETLEAF lf ON lf.MANDT='100' AND lf.SETCLASS=s.sc
                       AND lf.SUBCLASS=s.sub AND lf.SETNAME=s.sn
  INNER JOIN CSKB ce ON ce.MANDT='100' AND ce.KOKRS=s.kokrs
                    AND ce.KSTAR BETWEEN lf.VALFROM AND lf.VALTO
  LEFT JOIN TKB9D dt ON dt.MANDT='100' AND dt.ERSCH=s.ersch
                    AND dt.ERZUO=s.erzuo AND dt.SPRAS='E'
  LEFT JOIN SETHEADERT sh ON sh.MANDT='100' AND sh.SETCLASS='0102'
                         AND sh.SUBCLASS=s.sub AND sh.SETNAME=s.root_set
                         AND sh.LANGU='E'
  LEFT JOIN SKAT sk ON sk.MANDT='100' AND sk.SPRAS='E'
                   AND sk.KTOPL=s.sub AND sk.SAKNR=ce.KSTAR
 ORDER BY s.erzuo, ce.KSTAR;
CaseExcerpt of the expansion — assignment 010 (Personnel costs)
What it doesGroup INT-1 resolves through its child groups INT-1-1 / INT-1-2 / INT-1-3 into 20 individual cost elements. What section 4 showed as a single group name "INT-1" becomes the real account list.
Result20 rows (the assignment 010 part of the 169 rows)

Comments

Popular posts from this blog

SAP S/4HANA Table Reference — Tables I Actually Query, by Topic

Production Order Variance Calculation KKS1 — Fixing KV031 and KV011, and What Lands in COSB

Modern ABAP Syntax Notes — Patterns and Pitfalls from Real Development