Standard Cost Marking and Release Status — Reading It Straight from KEKO, MBEW and MBEWH

If you run a standard cost estimate every month, sooner or later someone asks a question no single SAP screen answers well: which materials were only marked, which were actually released, and when? Opening CK11N or CK24 material by material is not an answer when there are hundreds of them.

Three tables carry the whole story — KEKO, MBEW and MBEWH. Below are the queries I use to read status, monthly trend and missed releases straight out of them, in ABAP SQL and in native HANA SQL for the DB02 SQL editor.

The three states of a cost estimate

Estimating, marking and releasing are three separate acts, and each leaves its trace in a different field. Getting that mapping right is most of the job:

StatusHow it is recognisedWhat it means
ESTIMATED_ONLYFREIG ≠ 'X', VORMDAT = 00000000The estimate was run (CK11N/CK40N) and nothing else happened. The material master still carries the old price.
MARKED_NOT_RELEASEDFREIG ≠ 'X', VORMDAT > 00000000Marked (CK24 marking): MBEW-ZKPRS / ZKDAT carry the future price, but the current standard price is untouched.
RELEASEDFREIG = 'X', FREIDAT filledReleased (CK24 release): MBEW-STPRS carries the new standard price and MBEW-LAEPR is the release date.
The pitfall: KEKO-KKZMA looks like a marking indicator and is not one. Marking is VORMDAT; release is FREIG / FREIDAT.

Tables and fields

FieldDescriptionNote
KEKOCost estimate header. One row per material × costing date (KADKY).Filter with KALKA='01' (standard cost estimate), TVERS='01' (valuation version), LOEKZ = blank (not flagged for deletion).
KEKO-FREIGRelease of Standard Cost Estimate. 'X' once released.—
KEKO-FREIDAT / FREIUSRDate on Which Cost Estimate Released in Material Master / the user who released it.This is the field that carries the release date. It is not KADKY, the costing date.
KEKO-VORMDAT / VORMUSRDate on Which Cost Estimate Was Marked / the user who marked it.Marking is decided on this field.
KEKO-KKZMACosts Entered Manually in Additive or Automatic Cost Est.The name invites the assumption that this is the marking indicator. It is not — it flags manually entered additive costs. Do not use it to decide marking.
MBEW-STPRS / PEINHCurrent standard price / price unit. This is where a release lands.—
MBEW-ZKPRS / ZKDATFuture Price / Valid from. Marking writes the future standard price here.—
MBEW-LAEPRDate of Last Price Change. Updated by the release.Comparing it with KEKO-FREIDAT tells you whether the release actually reached the material master.
MBEW-VMSTP / VJSTPPrevious month / previous year standard price.—
MBEWHMaterial valuation history. A per-period snapshot (LFGJA/LFMON), which is where the monthly standard price trend comes from.The current period has not rolled into MBEWH yet, so the last row has to be topped up from MBEW.

Q1 · Latest estimate per material, with current status (ABAP SQL)

One row per material: released or not, release date, who released it, marking date, and the standard price sitting on the material master right now. The subquery on MAX(KADKY) is what keeps only the latest run. The RELEASE_CONSISTENCY column compares FREIDAT against LAEPR, so a release that never reached the material master shows up as CHECK_LAEPR.

SELECT
       k~matnr, k~werks, t~maktx AS material_text,
       k~kadky AS costing_date, k~kadat AS valid_from, k~bidat AS valid_to,
       k~kalst AS costing_level, k~losgr, k~meins, k~kalnr,

       "--- release / marking ---
       k~freig   AS released_flag,
       k~freidat AS released_on,
       k~freiusr AS released_by,
       k~vormdat AS marked_on,
       k~vormusr AS marked_by,
       CASE WHEN k~freig   = 'X'        THEN 'RELEASED'
            WHEN k~vormdat > '00000000' THEN 'MARKED_NOT_RELEASED'
            ELSE                             'ESTIMATED_ONLY'
       END AS cost_status,

       "--- price currently on the material master ---
       m~vprsv AS price_control,  m~stprs AS std_price_now,  m~peinh AS price_unit,
       m~zkprs AS future_price,   m~zkdat AS future_valid_from,
       m~laepr AS last_price_change,
       m~vmstp AS std_price_prev_month, m~vjstp AS std_price_prev_year,

       "--- release date vs last price change ---
       CASE WHEN k~freig = 'X' AND k~freidat = m~laepr THEN 'OK'
            WHEN k~freig = 'X'                         THEN 'CHECK_LAEPR'
            ELSE                                            ''
       END AS release_consistency

  FROM keko AS k
  LEFT OUTER JOIN mbew AS m
    ON m~matnr = k~matnr AND m~bwkey = k~bwkey AND m~bwtar = k~bwtar
  LEFT OUTER JOIN makt AS t
    ON t~matnr = k~matnr AND t~spras = @sy-langu
 WHERE k~kalka = '01' AND k~tvers = '01'
   AND k~werks = @p_werks
   AND k~loekz = @space
   AND k~kadky = ( SELECT MAX( k2~kadky ) FROM keko AS k2
                    WHERE k2~matnr = k~matnr AND k2~werks = k~werks
                      AND k2~bwkey = k~bwkey AND k2~bwtar = k~bwtar
                      AND k2~kalka = k~kalka AND k2~tvers = k~tvers
                      AND k2~loekz = @space )
 ORDER BY k~werks, k~matnr
  INTO TABLE @DATA(lt_current_status).

Q2 · Monthly trend of cost estimates (ABAP SQL)

The same source without the “latest only” restriction, so every monthly run stays in the result. substring(KADKY,1,6) gives YYYYMM for grouping, and dats_days_between measures how long each estimate sat between marking and release.

SELECT
       k~matnr, k~werks, t~maktx AS material_text,
       k~kadky                    AS costing_date,
       substring( k~kadky, 1, 6 ) AS costing_yyyymm,
       k~kadat AS valid_from, k~bidat AS valid_to,
       k~kalst AS costing_level, k~losgr, k~meins, k~kalnr,
       k~cpudt AS created_on, k~erfnm AS created_by,

       k~freig   AS released_flag,
       k~freidat AS released_on,
       k~freiusr AS released_by,
       k~vormdat AS marked_on,
       k~vormusr AS marked_by,
       CASE WHEN k~freig   = 'X'        THEN 'RELEASED'
            WHEN k~vormdat > '00000000' THEN 'MARKED_NOT_RELEASED'
            ELSE                             'ESTIMATED_ONLY'
       END AS cost_status,

       "days from marking to release
       CASE WHEN k~freig = 'X' AND k~vormdat > '00000000'
            THEN dats_days_between( k~vormdat, k~freidat )
            ELSE 0
       END AS days_mark_to_release

  FROM keko AS k
  LEFT OUTER JOIN makt AS t
    ON t~matnr = k~matnr AND t~spras = @sy-langu
 WHERE k~kalka = '01' AND k~tvers = '01'
   AND k~werks = @p_werks
   AND k~matnr = @p_matnr        "remove this line to read every material
   AND k~loekz = @space
   AND k~kadky BETWEEN @p_from AND @p_to
 ORDER BY k~werks, k~matnr, k~kadky
  INTO TABLE @DATA(lt_monthly_trend).

Q3 · Standard price history (ABAP SQL)

Q2 tells you when the estimate was run. Q3 tells you what the material master actually carried, period by period. MBEWH is written at period close, so the current period is missing — the second SELECT tops up the last row from MBEW.

SELECT
       h~matnr, h~bwkey AS valuation_area, h~bwtar AS valuation_type,
       h~lfgja AS fiscal_year, h~lfmon AS period,
       h~vprsv AS price_control,
       h~stprs AS std_price, h~verpr AS moving_avg_price, h~peinh AS price_unit,
       h~lbkum AS stock_qty, h~salk3 AS stock_value, h~bklas AS valuation_class
  FROM mbewh AS h
 WHERE h~matnr = @p_matnr
   AND h~bwkey = @p_werks
 ORDER BY h~matnr, h~bwkey, h~lfgja, h~lfmon
  INTO TABLE @DATA(lt_price_history).

" current period - not in MBEWH yet
SELECT SINGLE
       matnr, bwkey, bwtar, lfgja, lfmon,
       vprsv, stprs, verpr, peinh, lbkum, salk3, bklas,
       zkprs, zkdat, laepr, vmstp, vjstp
  FROM mbew
 WHERE matnr = @p_matnr
   AND bwkey = @p_werks
  INTO @DATA(ls_price_now).

Q4 · Finding the releases that were missed

Marked but never released. This is the month-end query: it lists exactly the materials whose CK24 release was skipped. Putting ZKPRS (future price) next to STPRS (current price) also shows what each price will become once the release runs.

SELECT
       k~matnr, k~werks, t~maktx AS material_text,
       k~kadky   AS costing_date,
       k~vormdat AS marked_on,
       k~vormusr AS marked_by,
       m~stprs   AS std_price_now,
       m~zkprs   AS future_price,
       m~zkdat   AS future_valid_from,
       m~laepr   AS last_price_change
  FROM keko AS k
  LEFT OUTER JOIN mbew AS m
    ON m~matnr = k~matnr AND m~bwkey = k~bwkey AND m~bwtar = k~bwtar
  LEFT OUTER JOIN makt AS t
    ON t~matnr = k~matnr AND t~spras = @sy-langu
 WHERE k~kalka   = '01'
   AND k~tvers   = '01'
   AND k~werks   = @p_werks
   AND k~loekz   = @space
   AND k~kadky   BETWEEN @p_from AND @p_to
   AND k~freig  <> 'X'                  "not released
   AND k~vormdat > '00000000'           "but marked
 ORDER BY k~werks, k~matnr, k~kadky
  INTO TABLE @DATA(lt_not_released).

Running the same thing in DB02 (native HANA SQL)

To paste these into DB02 → DBA Cockpit → Diagnostics → SQL Editor, four things have to change:

  • Add the MANDT condition yourself — there is no automatic client handling.
  • Join notation ~ becomes .
  • Host variables such as @p_werks become literals.
  • Drop INTO TABLE @DATA(...). Functions change too: dats_days_between → DAYS_BETWEEN(TO_DATE(...)).

▷ Q1 in native HANA SQL — edit the plant and client values directly in the WHERE clause.

SELECT
       K.MATNR, K.WERKS, T.MAKTX AS MATERIAL_TEXT,
       K.KADKY AS COSTING_DATE, K.KADAT AS VALID_FROM, K.BIDAT AS VALID_TO,
       K.KALST AS COSTING_LEVEL, K.LOSGR, K.MEINS, K.KALNR,

       K.FREIG   AS RELEASED_FLAG,
       K.FREIDAT AS RELEASED_ON,
       K.FREIUSR AS RELEASED_BY,
       K.VORMDAT AS MARKED_ON,
       K.VORMUSR AS MARKED_BY,
       CASE WHEN K.FREIG   = 'X'        THEN 'RELEASED'
            WHEN K.VORMDAT > '00000000' THEN 'MARKED_NOT_RELEASED'
            ELSE                             'ESTIMATED_ONLY'
       END AS COST_STATUS,

       M.VPRSV AS PRICE_CONTROL, M.STPRS AS STD_PRICE_NOW, M.PEINH AS PRICE_UNIT,
       M.ZKPRS AS FUTURE_PRICE,  M.ZKDAT AS FUTURE_VALID_FROM,
       M.LAEPR AS LAST_PRICE_CHANGE,
       M.VMSTP AS STD_PRICE_PREV_MONTH, M.VJSTP AS STD_PRICE_PREV_YEAR,

       CASE WHEN K.FREIG = 'X' AND K.FREIDAT = M.LAEPR THEN 'OK'
            WHEN K.FREIG = 'X'                         THEN 'CHECK_LAEPR'
            ELSE                                            ''
       END AS RELEASE_CONSISTENCY

  FROM KEKO AS K
  LEFT OUTER JOIN MBEW AS M
    ON  M.MANDT = K.MANDT AND M.MATNR = K.MATNR
    AND M.BWKEY = K.BWKEY AND M.BWTAR = K.BWTAR
  LEFT OUTER JOIN MAKT AS T
    ON  T.MANDT = K.MANDT AND T.MATNR = K.MATNR AND T.SPRAS = 'E'
 WHERE K.MANDT = '100'
   AND K.KALKA = '01'
   AND K.TVERS = '01'
   AND K.WERKS = '1010'
   AND K.LOEKZ = ''
   AND K.KADKY = ( SELECT MAX( K2.KADKY ) FROM KEKO AS K2
                    WHERE K2.MANDT = K.MANDT AND K2.MATNR = K.MATNR
                      AND K2.WERKS = K.WERKS AND K2.BWKEY = K.BWKEY
                      AND K2.BWTAR = K.BWTAR AND K2.KALKA = K.KALKA
                      AND K2.TVERS = K.TVERS AND K2.LOEKZ = '' )
 ORDER BY K.WERKS, K.MATNR;

▷ Q5 — monthly progress count — total, released, marked-only and estimated-only per plant and costing month. This is the one to watch during close.

SELECT
       K.WERKS,
       SUBSTRING( K.KADKY, 1, 6 )                            AS COSTING_YYYYMM,
       COUNT(*)                                              AS TOTAL_CNT,
       SUM( CASE WHEN K.FREIG = 'X' THEN 1 ELSE 0 END )      AS RELEASED_CNT,
       SUM( CASE WHEN K.FREIG <> 'X' AND K.VORMDAT > '00000000'
                 THEN 1 ELSE 0 END )                         AS MARKED_ONLY_CNT,
       SUM( CASE WHEN K.FREIG <> 'X' AND K.VORMDAT = '00000000'
                 THEN 1 ELSE 0 END )                         AS ESTIMATED_ONLY_CNT,
       MIN( CASE WHEN K.FREIG = 'X' THEN K.FREIDAT END )     AS FIRST_RELEASE_DATE,
       MAX( CASE WHEN K.FREIG = 'X' THEN K.FREIDAT END )     AS LAST_RELEASE_DATE
  FROM KEKO AS K
 WHERE K.MANDT = '100'
   AND K.KALKA = '01'
   AND K.TVERS = '01'
   AND K.WERKS = '1010'
   AND K.LOEKZ = ''
   AND K.KADKY BETWEEN '20250101' AND '20261231'
 GROUP BY K.WERKS, SUBSTRING( K.KADKY, 1, 6 )
 ORDER BY K.WERKS, COSTING_YYYYMM;

Which number is the released amount

The released amount is MBEW-STPRS / MBEWH-STPRS. An estimate that has not been released has no price on the material master, so its value has to come from KEPH (cost components KST001–KST120) or CKIS (itemization, sum of WERTN) — and those two do not agree with each other, because overhead is treated differently in each.

Measured on material FG228, costing date 20260301: CKIS sum 21,489.18 / 100 = 214.89, KEPH sum (KEART='H', KKZST='X') 21,743.82 / 100 = 217.44, actually released MBEW-STPRS = 216.00. If the point is to compare amounts, compare on MBEW / MBEWH.

KEPH also holds four to eight variant rows for the same KALNR / KADKY, keyed by the KEART / KKZST / KKZMA combination. Decide which rows you are summing before you sum them.

▷ CKIS itemization total — the sum is on the costing lot size (KEKO-LOSGR), so divide by LOSGR for a unit price.

SELECT SUM( wertn )
  FROM ckis
 WHERE kalnr = @lv_kalnr
   AND kalka = '01'
   AND kadky = @lv_kadky
   AND tvers = '01'
   AND bwvar = @lv_bwvar
  INTO @DATA(lv_total).

Tables: KEKO · MBEW · MBEWH · CKIS · KEPH · MAKT  |  Transactions: CK11N · CK24 · CK40N · DB02

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