ABAP

MCHB 직접 SELECT 금지 — CDS로 30초 전환 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 이 글에서 다루는 범위

S/4HANA 환경에서 배치(Batch) 관리 자재의 재고 조회는 오랫동안 MCHB, MCHA, MCH1 같은 클래식 테이블 직접 SELECT에 의존해 왔습니다. 그러나 SAP S/4HANA 1809 이후 배치별 재고 스냅샷은 MATDOC 기반의 CDS 뷰 I_InventoryBalanceByLot으로 재설계되어, MCHB 직접 조회는 마이그레이션 대상 코드로 분류됩니다. 이 글은 왜 MCHB를 버려야 하는지, 그리고 I_InventoryBalanceByLot을 실무 리포트/AMDP/RAP에서 어떻게 활용하는지 단계별로 설명합니다.

  • MCHB 직접 조회의 3대 리스크(권한/일관성/필드 확장) 진단
  • I_InventoryBalanceByLot의 조인 구조와 필드 시맨틱 파악
  • Open SQL, AMDP, RAP 커스텀 엔티티에서의 실전 활용 패턴
  • 배치 유효기간·역동 조회일 기준 필터링 기법

이 글을 읽기 전 알아두면 좋은 배경

ABAP CDS(DDIC 기반 View Entity, DEFINE VIEW ENTITY) 문법을 이해하고 있어야 하며, S/4HANA MM-IM 모듈의 재고 개념(비제한/품질/차단/이전 중)과 특수 재고 유형(K/O/Q/E) 정도는 알고 있어야 합니다. MATDOCMSEGMKPF를 통합한 단일 자재원장 테이블이라는 점, 그리고 액추얼 재고 스냅샷은 MATDOC_EXTRACT에서 산출된다는 점을 알면 이해가 빠릅니다.

실행 환경과 사전 준비

이 글의 코드는 다음 환경 기준으로 검증합니다.

  • SAP S/4HANA 2022 On-Premise 또는 S/4HANA Cloud Private Edition 2023
  • ABAP Platform 2022 (Kernel 789 이상)
  • ADT(ABAP Development Tools) 3.36 이상, Eclipse 2024-03
  • 권한 오브젝트: M_MATE_WRK, M_MSEG_WWA, S_TABU_DIS(display)
  • 테스트 자재는 배치 관리(MARA-XCHPF = 'X') + Plant 1010, Storage Location 101A 조합 권장

ADT에서 I_InventoryBalanceByLot을 열어 CDS 소스 아티팩트를 확인해두면, 아래 코드 예제의 필드 매핑을 실시간으로 대조할 수 있습니다.

핵심 개념: 왜 MCHB가 아니라 I_InventoryBalanceByLot 인가

MCHB는 클래식 ECC 시대에 "플랜트 + 저장위치 + 자재 + 배치" 조합의 재고 수량을 저장하던 애그리게이트 테이블입니다. S/4HANA로 오면서 재고 집계 로직은 MATDOCMATDOC_EXTRACT로 이관되었고, MCHB는 호환성 뷰(Compatibility View)로만 남아 있습니다. 다시 말해 MCHB를 SELECT하면 실제로는 뷰 뒤에서 MATDOC을 조회하는 오버헤드가 발생하며, 특정 케이스(예: 이전 중 재고, 특수 재고 파트너)에서는 기존 필드 매핑이 완벽하지 않을 수 있습니다.

비유하자면 MCHB는 "낡은 종이 장부를 찍은 사진"이고, I_InventoryBalanceByLot은 "실시간 클라우드 원장을 조회하는 API"입니다. 종이 장부는 지금도 존재하지만, 원본이 아니라 사본입니다.

I_InventoryBalanceByLotLot(배치) 단위 재고 잔량을 반환하는 표준 Basic View로, 내부적으로 다음 3계층 구조를 취합니다.

  1. I_MaterialStock 계열의 재고 스냅샷 뷰
  2. I_Batch - 배치 마스터(MCH1 대체)
  3. I_BatchByMaterial - 플랜트/자재별 배치 특성(MCHA 대체)

결과적으로 조회자는 재고 수량, 배치 유효기간(SLED/BBD), 제조일, 배치 상태를 단일 SELECT 한 번으로 얻을 수 있으며, CDS 권한 어노테이션 @AccessControl.authorizationCheck: #CHECK가 적용되어 있어 DCL(Data Control Language)로 사용자 권한이 자동 필터됩니다. 이는 MCHB 직접 SELECT 시 별도로 AUTHORITY-CHECK를 걸어야 했던 번거로움을 제거합니다.

핵심 필드 매핑을 정리하면 다음과 같습니다.

MCHB 필드I_InventoryBalanceByLot 필드비고
MATNRMaterial18자리 확장
WERKSPlant동일
LGORTStorageLocation동일
CHARGBatch동일
CLABSMatlWrhsStkQtyInMatlBaseUnit비제한 사용 가능
CINSMMatlWrhsStkInQualInspection품질 검사 중
CSPEMMatlWrhsStkInBlocked차단

1단계 실전 예제: 기본 배치 재고 조회

가장 먼저 특정 플랜트/자재의 배치별 재고를 조회하는 최소 코드입니다. 시나리오는 "화학 원료 CHEM-A001의 플랜트 1010 배치별 비제한 재고 조회"로 가정합니다.

REPORT z_batch_stock_basic.

DATA: lt_batch_stock TYPE TABLE OF i_inventorybalancebylot,
      lv_material    TYPE matnr VALUE 'CHEM-A001',
      lv_plant       TYPE werks_d VALUE '1010'.

SELECT Material,
       Plant,
       StorageLocation,
       Batch,
       InventorySpecialStockType,
       MatlWrhsStkQtyInMatlBaseUnit AS unrestricted_qty,
       MatlWrhsStkInQualInspection  AS quality_qty,
       MatlWrhsStkInBlocked         AS blocked_qty,
       MaterialBaseUnit
  FROM I_InventoryBalanceByLot
  WHERE Material = @lv_material
    AND Plant    = @lv_plant
    AND MatlWrhsStkQtyInMatlBaseUnit > 0
  ORDER BY Batch
  INTO TABLE @lt_batch_stock.

cl_demo_output=>display( lt_batch_stock ).

주의할 점은 MatlWrhsStkQtyInMatlBaseUnit > 0 필터입니다. MCHB에서는 잔량 0인 배치도 마스터가 살아있는 한 조회되었지만, I_InventoryBalanceByLot은 재고 이벤트가 있었던 배치를 모두 반환하므로 이월 잔량 0 레코드가 다수 포함됩니다. 실무에서는 반드시 잔량 필터를 걸어야 결과가 깔끔합니다.

2단계 실전 예제: 유효기간·집계·에러 처리를 포함한 실무 리포트

실무에서는 "SLED(자체 유효기간) 30일 이내 배치의 총 재고를 창고별로 집계"하는 요구가 자주 옵니다. 여기서는 I_InventoryBalanceByLotI_BatchByMaterial과 조인하여 유효기간을 얻고, 결과를 그룹핑합니다.

CLASS zcl_batch_expiry_report DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_expiry_line,
             plant           TYPE werks_d,
             storage_loc     TYPE lgort_d,
             material        TYPE matnr,
             batch           TYPE charg_d,
             sled            TYPE vfdat,
             days_to_expiry  TYPE i,
             total_qty       TYPE menge_d,
             base_unit       TYPE meins,
           END OF ty_expiry_line,
           tt_expiry_line TYPE STANDARD TABLE OF ty_expiry_line WITH EMPTY KEY.

    METHODS get_expiring_batches
      IMPORTING iv_plant        TYPE werks_d
                iv_days_horizon TYPE i DEFAULT 30
      RETURNING VALUE(rt_result) TYPE tt_expiry_line
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_batch_expiry_report IMPLEMENTATION.
  METHOD get_expiring_batches.

    DATA(lv_today)    = cl_abap_context_info=>get_system_date( ).
    DATA(lv_deadline) = CONV vfdat( lv_today + iv_days_horizon ).

    TRY.
        SELECT bal~Plant                                AS plant,
               bal~StorageLocation                      AS storage_loc,
               bal~Material                             AS material,
               bal~Batch                                AS batch,
               bat~ShelfLifeExpirationDate              AS sled,
               ( bat~ShelfLifeExpirationDate - @lv_today ) AS days_to_expiry,
               SUM( bal~MatlWrhsStkQtyInMatlBaseUnit )  AS total_qty,
               bal~MaterialBaseUnit                     AS base_unit
          FROM I_InventoryBalanceByLot AS bal
          INNER JOIN I_BatchByMaterial AS bat
            ON  bal~Material = bat~Material
            AND bal~Batch    = bat~Batch
            AND bal~Plant    = bat~Plant
          WHERE bal~Plant = @iv_plant
            AND bal~MatlWrhsStkQtyInMatlBaseUnit > 0
            AND bat~ShelfLifeExpirationDate BETWEEN @lv_today AND @lv_deadline
          GROUP BY bal~Plant, bal~StorageLocation, bal~Material,
                   bal~Batch, bat~ShelfLifeExpirationDate,
                   bal~MaterialBaseUnit
          ORDER BY sled
          INTO TABLE @rt_result.

      CATCH cx_sy_open_sql_db INTO DATA(lx_db).
        " 로깅: BAL(Application Log)에 기록
        MESSAGE ID 'ZBATCH' TYPE 'E' NUMBER '001'
          WITH lx_db->get_text( ) INTO DATA(lv_dummy).
        RAISE.
    ENDTRY.

  ENDMETHOD.
ENDCLASS.

days_to_expiry 계산에서 CDS 뷰가 아닌 Open SQL 레벨에서 산술을 수행하는 이유는, 조회일이 프로그램 실행 시점에 확정되기 때문입니다. 만약 뷰에 하드코딩하면 캐싱 이슈로 잘못된 값이 반환될 수 있습니다. 예외 처리는 cx_sy_open_sql_db로 통합하여 상위 컨트롤러에서 로깅합니다.

3단계 실전 예제: AMDP + RAP 언바운드 액션으로 프로덕션화

대량 배치(수십만 건 단위) 재고를 실시간 대시보드에 노출하려면 HANA 로컬 실행이 유리합니다. AMDP(ABAP Managed DB Procedure)로 감싸고, RAP 언바운드 액션으로 노출하는 프로덕션 패턴을 봅시다.

CLASS zcl_batch_stock_amdp DEFINITION
  PUBLIC FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_amdp_marker_hdb.

    TYPES: BEGIN OF ty_stock_agg,
             plant     TYPE werks_d,
             material  TYPE matnr,
             batch     TYPE charg_d,
             qty_sum   TYPE menge_d,
             lot_count TYPE i,
           END OF ty_stock_agg,
           tt_stock_agg TYPE STANDARD TABLE OF ty_stock_agg WITH EMPTY KEY.

    CLASS-METHODS aggregate_by_plant
      IMPORTING VALUE(iv_plant)     TYPE werks_d
                VALUE(iv_min_qty)   TYPE menge_d
      EXPORTING VALUE(et_result)    TYPE tt_stock_agg.
ENDCLASS.

CLASS zcl_batch_stock_amdp IMPLEMENTATION.
  METHOD aggregate_by_plant BY DATABASE PROCEDURE
                            FOR HDB LANGUAGE SQLSCRIPT
                            OPTIONS READ-ONLY
                            USING I_InventoryBalanceByLot.

    et_result =
      SELECT plant,
             material,
             batch,
             SUM( "MatlWrhsStkQtyInMatlBaseUnit" ) AS qty_sum,
             COUNT( DISTINCT "InventorySpecialStockType" ) AS lot_count
        FROM "I_InventoryBalanceByLot"
       WHERE plant = :iv_plant
         AND "MatlWrhsStkQtyInMatlBaseUnit" >= :iv_min_qty
       GROUP BY plant, material, batch;

  ENDMETHOD.
ENDCLASS.

AMDP는 ABAP 서버-DB 라운드트립을 제거하므로, 100K 이상의 배치를 집계할 때 Open SQL 대비 30~50% 성능 이득을 보이는 경우가 일반적입니다. 다만 @AccessControl.authorizationCheck 어노테이션이 #CHECK인 뷰를 AMDP에서 호출하면 권한이 우회될 수 있어, 반드시 호출부 ABAP에서 AUTHORITY-CHECK OBJECT 'M_MATE_WRK'를 선행해야 합니다.

보안·테스트 관점에서 프로덕션 배포 전 다음을 권장합니다.

  • ABAP Unit: osql_test_environmentI_InventoryBalanceByLot을 목킹하여 시나리오 격리
  • Static Check: ATC(ABAP Test Cockpit)에서 "Usage of Compatibility View (MCHB)" 룰 활성화
  • Runtime Trace: ST05 SQL 트레이스로 실제 실행계획 확인, MATDOC_EXTRACT 히트 여부 검증

흔한 실수와 트러블슈팅 FAQ

Q1. MCHB에는 있던 배치가 I_InventoryBalanceByLot에 안 나옵니다.
A. I_InventoryBalanceByLot은 재고 이벤트 기준으로 잔량을 산출합니다. 마스터만 존재하고 이동이 한 번도 없는 배치는 반환되지 않습니다. 배치 마스터 자체를 조회하려면 I_Batch 또는 I_BatchByMaterial을 사용하세요.

Q2. 특수 재고(고객 위탁 K, 벤더 위탁 O)가 중복 집계됩니다.
A. InventorySpecialStockType 필드로 반드시 그룹핑하거나 필터해야 합니다. 이 필드가 공백이면 자사 재고, K/O/Q/E는 각각 다른 소유권을 의미하므로 단순 SUM하면 회계상 왜곡이 생깁니다.

Q3. AMDP에서 필드명을 대문자로 썼는데 "invalid column name"이 납니다.
A. CDS 뷰의 SQL 이름은 케이스 센시티브입니다. 반드시 큰따옴표로 감싸고 MatlWrhsStkQtyInMatlBaseUnit처럼 CamelCase를 정확히 지켜야 합니다. ADT에서 뷰를 열고 F2로 SQL 이름을 확인하는 습관을 들이세요.

Q4. 성능이 오히려 MCHB보다 느립니다.
A. 필터 컬럼(Plant, Material, Batch)에 인덱스 힌트가 밀리는 경우가 있습니다. WHERE 절 순서를 키 필드 우선으로 재배치하고, %_HINTS HDB 'USE_HEX_PLAN'을 시도해보세요. 또한 SELECT * 대신 필요한 컬럼만 지정하면 HANA 컬럼 스토어 특성상 큰 개선이 있습니다.

여기서 더 나아가려면

이 글에서 다룬 I_InventoryBalanceByLot을 마스터했다면 다음 영역으로 확장할 수 있습니다.

  • I_MaterialStock - 배치 없는 자재의 총량 재고 조회 (MARD/MARC 대체)
  • I_StockInTransit - 이전 중 재고(intra/inter-company) 전용 뷰
  • C_MaterialStockAvailability - Consumption View로 Fiori Elements 앱 직결
  • Embedded Analytics: I_InventoryBalanceByLot을 Analytical Query로 래핑하여 KPI Modeler에 노출
  • RAP 커스텀 엔티티에서 Behavior Definition에 재고 이동 액션 추가 (예: 배치 재분류)

더 읽어볼 자료

댓글 0

아직 댓글이 없습니다.