개요 및 이 글에서 얻어갈 것
SAP S/4HANA 환경에서 자재 재고를 조회할 때 여전히 MARD 테이블을 직접 SELECT 하는 코드가 남아있는 프로젝트가 많습니다. 하지만 S/4HANA로 넘어오면서 재고 데이터의 저장 구조가 변경되었고, MATDOC 기반의 aggregation 로직이 도입되면서 MARD를 그대로 참조하는 것은 여러 부작용을 일으킵니다. 이 글에서는 I_MaterialStock Virtual Data Model(VDM) CDS View를 통해 재고 현황을 안전하고 정확하게 조회하는 방법을 다룹니다.
- MARD 직접 조회의 실무적 문제점과 S/4HANA에서의 변화 이해
- I_MaterialStock CDS View의 필드 구조와 aggregation 동작 원리 파악
- ABAP Open SQL로 Plant/Storage Location별 재고 조회 예제 작성
- 대량 데이터 조회 시 성능·권한·트러블슈팅 관점의 실전 노하우 확보
이 글을 읽기 전 알아두면 좋은 것
ABAP Open SQL 기본 문법(SELECT, JOIN, WHERE), CDS View의 개념(annotation, association, view entity), 그리고 SAP MM 모듈의 자재 마스터(MARA)와 재고 테이블(MARD, MSEG, MATDOC)에 대한 기초적인 이해가 있으면 흐름을 따라오기 수월합니다. Eclipse ADT(ABAP Development Tools) 사용 경험이 있으면 CDS View 소스를 직접 F2로 열어보며 이해도를 높일 수 있습니다.
환경 및 버전 확인 사항
본 예제는 다음 환경에서 검증한 코드를 기준으로 작성했습니다.
- SAP S/4HANA 2022 On-Premise 또는 SAP S/4HANA Cloud, private edition (2023 이상 권장)
- ABAP Platform 2022 (7.58) 이상 — view entity(
DEFINE VIEW ENTITY) 문법 지원 - Eclipse ADT 3.34 이상, ABAP for Eclipse plugin 최신 버전
- SAP GUI 7.70 PL15 이상 (SE16, SE11 대체 확인용)
- 권한 오브젝트:
M_MATE_WRK,M_MATE_LGO— 자재/저장위치 조회 권한 필요
S/4HANA 이전(ECC) 환경에서는 I_MaterialStock이 존재하지 않을 수 있으며, MARD 직접 조회 방식이 여전히 유효합니다. 반드시 시스템의 SAP_BASIS 및 SAP_APPL 컴포넌트 릴리즈를 확인한 뒤 진행하세요. Cloud edition 릴리즈 정책상 CDS View의 필드는 마이너 릴리즈에서 확장될 수 있으므로, 실제 배포 전 target 시스템에서 필드 존재 여부를 다시 검증하는 것을 권장합니다.
MARD를 왜 쓰지 말라고 할까 — 핵심 개념 이해
ECC 시절 MARD는 "Plant + Storage Location + Material" 단위의 재고 스냅샷을 그대로 저장하는 테이블이었습니다. 재고 이동이 발생하면 MSEG(문서 라인) 뿐 아니라 MARD의 LABST(가용재고), INSME(품질검사 재고), SPEME(블록 재고) 등이 실시간으로 갱신되었습니다. 개발자는 그저 SELECT ... FROM mard 한 줄이면 현재 재고를 알 수 있었죠.
S/4HANA는 이 구조를 완전히 뒤집었습니다. 재고 관련 aggregate 테이블(MARD, MARC의 재고 필드, MCHB 등)은 실제로는 MATDOC(Material Document) 테이블 위에 얹힌 compatibility view로 재구성되었습니다. 즉 MARD는 이제 물리 테이블이 아니라 뷰에 가깝게 동작하며, 조회 시점에 MATDOC의 문서 라인을 실시간 집계해 값을 만들어냅니다.
비유하자면 예전
MARD가 "매 시간마다 창고 담당자가 손으로 갱신하는 화이트보드"였다면, S/4HANA의MARD는 "질문할 때마다 CCTV 영상을 되감아 세어 알려주는 자동응답기"에 가깝습니다. 화이트보드와 인터페이스는 같지만, 뒤에서 벌어지는 일이 완전히 다릅니다.
이 구조 변화로 인해 MARD를 직접 조회할 때 세 가지 문제가 발생합니다. 첫째, 필드 semantic이 예전과 미묘하게 달라질 수 있고 향후 릴리즈에서 deprecated 처리될 위험이 있습니다. 둘째, MARD는 재고 필드만 갖고 있어 자재 텍스트, 단위, MRP 그룹 같은 필드를 얻으려면 MARA, MAKT, MARC와 JOIN을 반복해야 합니다. 셋째, 권한 체크(AUTHORITY-CHECK)를 개발자가 직접 코딩해야 하므로 누락 시 보안 이슈로 이어집니다.
I_MaterialStock은 이 문제들을 한 번에 해결하기 위해 SAP가 제공하는 표준 VDM Basic View입니다. 내부적으로 NSDM_V_MARD 또는 그에 상응하는 aggregation view를 참조하되, 표준화된 필드명(예: Material, Plant, StorageLocation, MatlWrhsStkQtyInMatlBaseUnit)을 노출합니다. 또한 association을 통해 _Material, _Plant, _StorageLocation 같은 관련 뷰로 자연스럽게 접근할 수 있어 JOIN 지옥에서 벗어날 수 있습니다.
1단계 예제 — 특정 자재의 총 재고 조회
가장 단순한 시나리오부터 시작합니다. 사용자가 자재 번호를 하나 입력하면 전 사업장 합계 재고를 반환하는 리포트입니다.
REPORT z_stock_single_material.
PARAMETERS: p_matnr TYPE matnr OBLIGATORY.
DATA: lv_total_stock TYPE p LENGTH 15 DECIMALS 3,
lv_base_unit TYPE meins.
SELECT SINGLE
SUM( MatlWrhsStkQtyInMatlBaseUnit ) AS total_qty,
MAX( MaterialBaseUnit ) AS base_unit
FROM I_MaterialStock
WHERE Material = @p_matnr
INTO ( @lv_total_stock, @lv_base_unit ).
IF sy-subrc = 0.
WRITE: / '자재', p_matnr,
/ '총 재고:', lv_total_stock, lv_base_unit.
ELSE.
WRITE: / '재고 데이터가 존재하지 않습니다.'.
ENDIF.
핵심은 필드명이 기술적 코드(LABST)가 아니라 semantic한 이름(MatlWrhsStkQtyInMatlBaseUnit)이라는 점입니다. 처음엔 길어서 불편하게 느껴지지만, 몇 개월 후 코드를 다시 볼 때 "이게 뭐하는 컬럼이지?" 하는 시간을 획기적으로 줄여줍니다.
2단계 예제 — Plant/Storage Location별 재고 집계와 에러 핸들링
실무에서는 단일 자재보다 "특정 사업장의 전 자재 재고 리스트" 또는 "저장위치별 재고 분포"가 훨씬 자주 요구됩니다. 이번엔 자재 텍스트, 단위, 그리고 재고가 0 이상인 라인만 필터링하는 시나리오를 구현합니다.
CLASS zcl_stock_by_plant DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_stock_line,
material TYPE matnr,
description TYPE maktx,
plant TYPE werks_d,
stor_loc TYPE lgort_d,
quantity TYPE p LENGTH 15 DECIMALS 3,
base_unit TYPE meins,
END OF ty_stock_line,
tt_stock TYPE STANDARD TABLE OF ty_stock_line WITH EMPTY KEY.
METHODS get_stock_by_plant
IMPORTING iv_plant TYPE werks_d
RETURNING VALUE(rt_stock) TYPE tt_stock
RAISING cx_sy_open_sql_db.
ENDCLASS.
CLASS zcl_stock_by_plant IMPLEMENTATION.
METHOD get_stock_by_plant.
TRY.
SELECT
ms~Material AS material,
mt~MaterialName AS description,
ms~Plant AS plant,
ms~StorageLocation AS stor_loc,
SUM( ms~MatlWrhsStkQtyInMatlBaseUnit ) AS quantity,
MAX( ms~MaterialBaseUnit ) AS base_unit
FROM I_MaterialStock AS ms
LEFT OUTER JOIN I_Material AS mt
ON ms~Material = mt~Material
WHERE ms~Plant = @iv_plant
AND ms~MatlWrhsStkQtyInMatlBaseUnit > 0
GROUP BY ms~Material, mt~MaterialName,
ms~Plant, ms~StorageLocation
ORDER BY ms~StorageLocation, ms~Material
INTO TABLE @rt_stock.
IF sy-subrc <> 0.
MESSAGE i398(00) WITH 'Plant' iv_plant '재고 없음' ''.
ENDIF.
CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
" 실무 로깅 — Application Log(SLG1) 기록 권장
MESSAGE lx_sql->get_text( ) TYPE 'E'.
RAISE EXCEPTION lx_sql.
ENDTRY.
ENDMETHOD.
ENDCLASS.
몇 가지 실전 포인트를 짚어보면, I_Material과의 LEFT OUTER JOIN을 통해 자재 텍스트를 함께 가져오되, 자재가 마스터에 없더라도 재고 라인은 유지되도록 했습니다. MatlWrhsStkQtyInMatlBaseUnit > 0 조건은 재고가 없는(0인) 라인이 대량으로 나오는 것을 방지합니다. 실제 창고에서는 한 번이라도 재고 이동이 있었던 자재는 0이 되어도 라인이 남기 때문에 이 필터가 성능에 큰 도움이 됩니다.
3단계 예제 — 프로덕션급 조회 클래스와 성능·보안 고려
대량 데이터를 다루는 배치 리포트나 OData 서비스 백엔드에서는 페이지네이션, 권한 체크, 그리고 캐싱 여부까지 고려해야 합니다.
CLASS zcl_stock_service DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_filter,
plant_range TYPE RANGE OF werks_d,
stor_loc_range TYPE RANGE OF lgort_d,
material_range TYPE RANGE OF matnr,
min_quantity TYPE menge_d,
END OF ty_filter,
BEGIN OF ty_result,
material TYPE matnr,
plant TYPE werks_d,
stor_loc TYPE lgort_d,
quantity TYPE menge_d,
base_unit TYPE meins,
END OF ty_result,
tt_result TYPE STANDARD TABLE OF ty_result WITH EMPTY KEY.
METHODS query
IMPORTING is_filter TYPE ty_filter
iv_offset TYPE i DEFAULT 0
iv_max_rows TYPE i DEFAULT 500
RETURNING VALUE(rt_lines) TYPE tt_result
RAISING cx_sy_authorization_error
cx_sy_open_sql_db.
ENDCLASS.
CLASS zcl_stock_service IMPLEMENTATION.
METHOD query.
" 1) 권한 체크 — Plant 오브젝트 M_MATE_WRK
LOOP AT is_filter-plant_range INTO DATA(ls_plant).
AUTHORITY-CHECK OBJECT 'M_MATE_WRK'
ID 'WERKS' FIELD ls_plant-low
ID 'ACTVT' FIELD '03'.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_sy_authorization_error.
ENDIF.
ENDLOOP.
" 2) 실제 조회 — UP TO / OFFSET 페이지네이션
TRY.
SELECT
Material AS material,
Plant AS plant,
StorageLocation AS stor_loc,
SUM( MatlWrhsStkQtyInMatlBaseUnit ) AS quantity,
MAX( MaterialBaseUnit ) AS base_unit
FROM I_MaterialStock
WHERE Plant IN @is_filter-plant_range
AND StorageLocation IN @is_filter-stor_loc_range
AND Material IN @is_filter-material_range
GROUP BY Material, Plant, StorageLocation
HAVING SUM( MatlWrhsStkQtyInMatlBaseUnit ) >= @is_filter-min_quantity
ORDER BY Plant, StorageLocation, Material
INTO TABLE @rt_lines
OFFSET @iv_offset
UP TO @iv_max_rows ROWS.
CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
RAISE EXCEPTION lx_sql.
ENDTRY.
ENDMETHOD.
ENDCLASS.
이 버전에서 주목할 부분은 세 가지입니다. 첫째, AUTHORITY-CHECK를 명시적으로 수행해 서비스 계층에서 권한을 이중 확인합니다. 둘째, OFFSET ... UP TO ROWS로 페이지네이션을 구현해 OData $top/$skip과 자연스럽게 매핑됩니다. 셋째, HAVING 절로 집계 후 필터링을 수행하여 "최소 재고 이상만 보고 싶다"는 요건을 DB 계층에서 해결합니다. 어플리케이션 서버로 데이터를 다 가져와 DELETE lt_result WHERE ...로 걸러내는 흔한 안티패턴을 피할 수 있습니다.
단위 테스트는 CL_OSQL_TEST_ENVIRONMENT(ABAP Test Double Framework)로 I_MaterialStock을 mock 처리한 뒤, 예상 결과가 나오는지 검증하는 방식을 권장합니다.
자주 부딪히는 함정과 FAQ
실제 프로젝트에서 자주 마주치는 이슈를 정리해봅니다.
- Q1. I_MaterialStock을 SELECT 했는데 재고가 0으로 나옵니다. 대부분의 경우 Special Stock(예: Consignment, Project Stock)이 별도로 관리되기 때문입니다.
I_MaterialStock은 unrestricted stock 중심이며, 특별재고까지 포함하려면I_MatlStkInAcctMod또는I_StockQuantityCurrentValue같은 확장 뷰를 검토해야 합니다. - Q2. 필드명이 너무 길어 코드가 지저분합니다. Open SQL의 alias(
AS qty)를 적극 활용하고, 반복 참조되는 뷰는 별도 컨슈머 CDS View(Z_C_MaterialStock)를 정의해 프로젝트 표준 필드명으로 재노출하는 방법을 권장합니다. - Q3. 성능이 MARD 직접 조회보다 느립니다. S/4HANA on HANA에서
I_MaterialStock은 aggregation view이므로 대량 WHERE 없이 전체 스캔하면 느릴 수 있습니다. 반드시Plant와Material중 하나는 인덱스 친화적인 WHERE 조건으로 걸어야 하며, ST05 SQL Trace로 실행 계획을 확인하는 습관이 필요합니다.
추가로, deprecated된 C_MaterialStock(consumption view)과 I_MaterialStock(interface view)을 혼동하지 않도록 주의하세요. 신규 개발에는 반드시 I_ 접두 basic view를, 신규 UI/Analytics 소비에는 별도 C_ 뷰를 사용하는 것이 권장 패턴입니다.
이어서 살펴보면 좋은 주제들
재고 데이터를 조회할 수 있게 되었다면 다음으로 확장할 방향은 명확합니다. 재고 변동 이력이 필요하다면 I_MatlDocumentItem(MATDOC 기반)을 살펴보고, 재고 가치평가가 관심사라면 I_MaterialLedger와 I_ProductValuation을, 실시간 대시보드가 목표라면 C_MaterialStockActual 계열의 analytical view와 SAC(SAP Analytics Cloud) live connection을 검토해볼 수 있습니다. 또한 RAP(Restful Application Programming Model)로 재고 조회 OData 서비스를 만드는 예제로 확장하면, Fiori 앱까지 이어지는 end-to-end 흐름을 경험할 수 있습니다.
더 깊이 파고들 수 있는 자료 링크
- SAP Help Portal — SAP S/4HANA Virtual Data Model (VDM)
- SAP Help Portal — SAP S/4HANA Cloud, CDS Views Reference
- SAP Help Portal — ABAP CDS View Entity Development Guide
- SAP Community Blogs — Inventory Management on S/4HANA
- SAP Business Accelerator Hub — Material Stock APIs
- SAP Note 2206980 — S/4HANA: Simplification of Material Inventory Management
- SAP Note 2933282 — I_MaterialStock: performance recommendations
댓글 0
아직 댓글이 없습니다.