I_Material이 필요해진 이유 — CDS View의 등장 배경
SAP S/4HANA에서 자재 마스터를 다뤄본 개발자라면 MARA와 MAKT를 JOIN하는 SELECT문을 수없이 작성해봤을 것입니다. 그런데 S/4HANA 1610 이후 SAP는 I_Material이라는 표준 CDS View를 제공하기 시작했고, 이제는 자재 조회 로직을 작성할 때 원천 테이블을 직접 건드리기보다 이 뷰를 활용하는 방향이 권장됩니다. 이 글에서는 I_Material이 무엇이고, 왜 MARA/MAKT 직접 조회 대신 사용해야 하는지, 그리고 실무 코드에서 어떻게 활용하는지를 단계적으로 다룹니다.
이 글을 다 읽고 나면 다음을 할 수 있게 됩니다.
- I_Material 뷰의 계층 구조(Basic / Composite / Consumption)와 명명 규칙 이해
- MARA/MAKT 직접 조회 대비 CDS View 사용의 장단점 비교
- ABAP SQL에서 I_Material 조회 코드 작성 및 성능 최적화
- 구매오더·생산오더 등 실무 시나리오에 자재 정보 결합하기
- 다국어 텍스트 필드를 올바르게 다루는 방법
읽기 전에 알고 있으면 좋은 것
이 글은 ABAP OpenSQL 기본 문법(SELECT, WHERE, INNER JOIN)과 자재 마스터 개념(자재번호, 자재유형, 플랜트, 자재그룹)을 알고 있다는 전제 하에 진행됩니다. CDS View 문법 자체는 몰라도 상관없지만, ADT(ABAP Development Tools in Eclipse) 환경에서 뷰를 탐색해 본 경험이 있으면 이해가 빠릅니다. 또한 @Data.Semantics, @ObjectModel 같은 어노테이션 개념을 어렴풋이라도 접해봤다면 좋습니다.
실습 환경과 준비물
이 글의 코드는 다음 환경을 기준으로 작성했습니다. 온프레미스 S/4HANA와 클라우드 에디션에서 뷰의 필드 구성이 약간 다를 수 있으니, 자신의 시스템에서 ADT의 Data Preview로 필드를 확인한 뒤 코드를 작성하는 것을 권장합니다.
- SAP S/4HANA 2022 FPS02 이상 또는 S/4HANA Cloud Public Edition 2402 이상
- ABAP Platform 7.58 이상 (ABAP SQL 최신 문법 지원)
- ABAP Development Tools for Eclipse 3.36 이상
- 권한:
M_MATE_MAT(ACTVT 03),S_DEVELOP - 테스트용 자재 데이터: 자재유형 FERT, HALB, ROH 각각 몇 건
ABAP Cloud(RAP, Steampunk) 환경에서는 MARA/MAKT 직접 접근이 원천 차단됩니다. 릴리스된 CDS View만이 유일한 진입점이므로, 처음부터 I_Material 기반 코드로 습관을 들이는 편이 이후 마이그레이션 부담을 크게 줄여 줍니다.
MARA·MAKT를 직접 조회했을 때 부딪히는 문제
전통적인 방식으로 자재번호와 자재명을 조회하는 코드는 다음과 비슷합니다.
" 안티 패턴: MARA/MAKT 직접 접근
SELECT m~matnr, m~mtart, m~meins, m~matkl,
t~maktx
FROM mara AS m
INNER JOIN makt AS t
ON m~matnr = t~matnr
AND t~spras = @sy-langu
WHERE m~mtart = 'FERT'
INTO TABLE @DATA(lt_legacy_material).
겉보기엔 문제없어 보이지만, 실무에 들어가면 다음 함정들이 하나둘씩 드러납니다. 첫째, MARA는 200개가 넘는 필드를 갖고 있어서 필요한 필드만 명시하지 않으면 불필요한 I/O가 발생합니다. 둘째, MAKT는 언어별 텍스트 테이블이므로 SPRAS 조건을 매번 걸어야 하고, 다국어 시나리오에서는 종종 누락됩니다. 셋째, 자재 상태(MSTAE), 삭제 플래그(LVORM) 같은 필터링이 코드마다 제각각이라 일관성이 무너집니다. 넷째, S/4HANA에서 자재번호 길이가 40자리로 확장되면서 기존 코드가 곳곳에서 트렁케이션 이슈를 일으켰습니다. I_Material은 이런 문제들을 표준화된 시맨틱 계층에서 한 번에 해결해 줍니다.
I_Material 뷰의 구조와 주요 필드
SAP의 Virtual Data Model(VDM)은 뷰를 세 계층으로 나눕니다. 마치 데이터베이스 원본을 여러 겹의 필터로 감싼 양파와 같습니다.
[Consumption Layer] C_Material... <- Fiori/OData 소비용
|
[Interface Layer] I_Material <- 재사용 가능한 표준 뷰 (개발자가 SELECT)
|
[Basic Layer] 기본 조합 뷰 <- 순수 데이터 조합
|
[Physical Tables] MARA / MAKT / MARC <- 물리 테이블 (직접 접근 지양)
I_Material의 주요 필드는 다음과 같습니다. ADT에서 뷰를 열면 필드마다 어노테이션이 붙어 있는데, 이 정보가 곧 시맨틱을 제공합니다.
| MARA 컬럼 | I_Material 필드 | 비고 |
|---|---|---|
| MATNR | Material | 자재번호 40자 확장 반영 |
| MTART | MaterialType | FERT/HALB/ROH 등 |
| MATKL | MaterialGroup | 자재그룹 |
| MEINS | BaseUnit | 기본계량단위 |
| LVORM | MaterialIsMarkedForDeletion | Boolean으로 변환 |
| ERSDA | CreationDate | - |
| LAEDA | LastChangeDate | - |
| MAKT-MAKTX | I_MaterialText.MaterialName | 텍스트는 별도 뷰 |
텍스트는 별도의 텍스트 뷰인 I_MaterialText에서 관리하며, I_Material과 언어 종속으로 연결됩니다. 이 분리 덕분에 언어 처리가 뷰 내부에서 표준화되어 있어, 개발자가 SPRAS 필터를 실수로 빠뜨리는 사고를 원천적으로 줄일 수 있습니다.
실전 코드 1단계 — 기본 조회 패턴
가장 단순한 형태부터 시작합니다. 특정 플랜트 관계없이, 자재유형이 반제품(HALB)인 자재를 최근 생성 순으로 상위 100건 뽑는 예제입니다.
DATA lt_semi_finished TYPE TABLE OF i_material.
SELECT material,
materialtype,
materialgroup,
baseunit,
creationdate
FROM i_material
WHERE materialtype = 'HALB'
AND materialismarkedfordeletion = @abap_false
ORDER BY creationdate DESCENDING
INTO CORRESPONDING FIELDS OF TABLE @lt_semi_finished
UP TO 100 ROWS.
cl_demo_output=>display( lt_semi_finished ).
주목할 점은 materialismarkedfordeletion을 abap_false와 비교하는 부분입니다. 원본 MARA의 LVORM은 ' '/'X'였지만, CDS View 계층에서 이미 Boolean 시맨틱으로 변환됩니다. 이런 캐스팅을 뷰가 대신 해주는 점도 표준 뷰 사용의 장점 중 하나입니다.
실전 코드 2단계 — 구매오더 품목에 자재 정보 결합, 예외 처리와 로깅
실무에서 훨씬 자주 마주치는 시나리오입니다. 특정 구매오더 목록에 대해 각 품목의 자재유형과 자재그룹을 함께 가져와 리포트를 만들어야 한다고 가정합니다. 여기서는 예외 처리와 로깅까지 넣어 봅니다.
CLASS zcl_po_material_reporter DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_po_material_row,
purchase_order TYPE ebeln,
purchase_order_item TYPE ebelp,
material TYPE matnr,
material_type TYPE mtart,
material_group TYPE matkl,
base_unit TYPE meins,
order_quantity TYPE menge_d,
net_price_amount TYPE bprei,
END OF ty_po_material_row,
tt_po_material_row TYPE STANDARD TABLE OF ty_po_material_row
WITH EMPTY KEY.
METHODS fetch_po_item_with_material
IMPORTING it_purchase_orders TYPE range_t_ebeln
RETURNING VALUE(rt_result) TYPE tt_po_material_row
RAISING zcx_po_material_fetch.
ENDCLASS.
CLASS zcl_po_material_reporter IMPLEMENTATION.
METHOD fetch_po_item_with_material.
IF it_purchase_orders IS INITIAL.
RAISE EXCEPTION TYPE zcx_po_material_fetch
EXPORTING textid = zcx_po_material_fetch=>empty_input.
ENDIF.
TRY.
SELECT po~purchaseorder,
po~purchaseorderitem,
po~material,
mat~materialtype,
mat~materialgroup,
mat~baseunit,
po~orderquantity,
po~netpriceamount
FROM i_purchaseorderitem AS po
INNER JOIN i_material AS mat
ON po~material = mat~material
WHERE po~purchaseorder IN @it_purchase_orders
AND mat~materialismarkedfordeletion = @abap_false
INTO CORRESPONDING FIELDS OF TABLE @rt_result.
IF sy-subrc <> 0.
DATA(lo_log) = zcl_app_logger=>get_instance( 'ZPO_MATERIAL' ).
lo_log->info( |No purchase order data found| ).
RETURN.
ENDIF.
CATCH cx_sy_open_sql_db INTO DATA(lx_db).
DATA(lo_err_log) = zcl_app_logger=>get_instance( 'ZPO_MATERIAL' ).
lo_err_log->error( iv_message = lx_db->get_text( ) ).
RAISE EXCEPTION TYPE zcx_po_material_fetch
EXPORTING previous = lx_db.
ENDTRY.
ENDMETHOD.
ENDCLASS.
이 코드에서 눈여겨볼 부분은 세 가지입니다. 첫째, I_PurchaseOrderItem과 I_Material이 모두 표준 뷰이므로 JOIN 키인 Material 필드가 시맨틱적으로 동일함이 보장됩니다. 둘째, IN @it_purchase_orders로 RANGE를 넘겨 건별 SELECT 대신 대량 조회를 수행합니다. 셋째, DB 예외를 애플리케이션 로그로 남겨 운영 중 추적성을 확보합니다.
실전 코드 3단계 — 프로덕션 관점의 성능·다국어·인가 처리
대량 처리와 다국어를 동시에 다루면서 인가 체크까지 넣은 형태로 확장합니다. 특히 텍스트는 I_MaterialText를 활용해 언어 종속으로 조회합니다.
CLASS zcl_production_material_view DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_prod_order_display,
manufacturing_order TYPE aufnr,
manufacturing_order_item TYPE posnr,
material TYPE matnr,
production_plant TYPE werks_d,
material_type TYPE mtart,
material_group TYPE matkl,
base_unit TYPE meins,
material_name TYPE string,
planned_total_quantity TYPE menge_d,
END OF ty_prod_order_display,
tt_prod_order_display TYPE STANDARD TABLE OF ty_prod_order_display
WITH EMPTY KEY.
METHODS build_production_order_view
IMPORTING iv_plant TYPE werks_d
iv_language TYPE spras OPTIONAL
RETURNING VALUE(rt_view) TYPE tt_prod_order_display
RAISING zcx_auth_missing
cx_sy_open_sql_db.
ENDCLASS.
CLASS zcl_production_material_view IMPLEMENTATION.
METHOD build_production_order_view.
DATA lv_langu TYPE spras.
lv_langu = COND #( WHEN iv_language IS INITIAL
THEN sy-langu
ELSE iv_language ).
" 인가 체크 - 자재 마스터 조회 권한
AUTHORITY-CHECK OBJECT 'M_MATE_MAT'
ID 'ACTVT' FIELD '03'
ID 'MATNR' DUMMY.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE zcx_auth_missing.
ENDIF.
SELECT po~manufacturingorder,
po~manufacturingorderitem,
po~material,
po~productionplant,
mat~materialtype,
mat~materialgroup,
mat~baseunit,
txt~materialname,
po~mfgorderplannedtotalqty AS planned_total_quantity
FROM i_manufacturingorder AS po
INNER JOIN i_material AS mat
ON po~material = mat~material
LEFT OUTER JOIN i_materialtext AS txt
ON txt~material = mat~material
AND txt~language = @lv_langu
WHERE po~productionplant = @iv_plant
AND po~manufacturingorderstatus = 'REL'
AND mat~materialismarkedfordeletion = @abap_false
ORDER BY po~manufacturingorder
INTO CORRESPONDING FIELDS OF TABLE @rt_view
PACKAGE SIZE 500.
" 청크 단위 후처리 - 대량 조회 시 메모리 보호
" (실제 구현에서는 여기서 결과를 스트림 처리)
CLEAR: rt_view.
ENDSELECT.
ENDMETHOD.
ENDCLASS.
여기서 LEFT OUTER JOIN으로 텍스트를 붙인 이유는, 다국어 환경에서 특정 언어 텍스트가 아직 유지관리되지 않은 자재도 결과에 포함시켜야 하기 때문입니다. INNER JOIN으로 붙였다면 텍스트가 없는 자재가 통째로 빠져 리포트에서 사라지는 사고가 발생합니다. 단위 테스트에서는 ABAP Unit + Test Double Framework로 I_Material을 목킹해 DB 없이 로직을 검증할 수 있습니다.
자주 밟는 지뢰와 대처법
실무에서 반복적으로 관찰되는 실수들을 정리합니다.
- Q1. I_Material에 플랜트 정보가 없어요.
I_Material은 클라이언트 수준의 자재 마스터입니다. 플랜트별 정보는I_ProductPlant또는I_MaterialValuation에 있습니다. 두 뷰를Material키로 JOIN하세요. - Q2. 성능이 오히려 느려진 것 같습니다.
CDS View는 여러 테이블을 조인한 결과이므로 필요한 필드를 최소한으로 SELECT하고, WHERE 조건에 인덱스 컬럼(Material,MaterialType)을 반드시 포함하세요. ST05로 실행 계획을 확인하고, 필요하면@AbapCatalog.buffering상태를 점검합니다. - Q3. 자재유형 코드가 언어별로 다르게 나올 수 있나요?
코드 자체는 언어와 무관합니다. 다만 자재유형의 설명은I_MaterialTypeText를 별도로 조회해야 합니다. - Q4.
SELECT SINGLE ... WHERE Material = '100234'가 값을 못 찾습니다.
Material필드는 40자리로 확장되었지만, 내부적으로 여전히 ALPHA conversion routine이 붙어 있습니다. Literal 대신CONV matnr( '100234' )로 변환하거나MATN1함수 모듈을 통해 정규화하세요.
이 밖에도 대소문자 문제가 자주 나옵니다. CDS 뷰의 필드는 카멜케이스(MaterialGroup)로 정의되어 있지만 ABAP SQL에서는 대소문자를 구분하지 않으므로 materialgroup으로 써도 무방합니다. 반면 뷰 이름 자체(i_material)는 관례상 소문자로 쓰는 편이 가독성이 좋습니다. 또한 릴리즈 상태(C1/C2/C3)를 확인하지 않고 사용하면, 클라우드 마이그레이션 시 해당 뷰가 사라질 수 있으니 ADT의 API State 뷰어에서 항상 확인하세요.
이어서 살펴볼 만한 주제
여기까지 익혔다면 다음 단계로 넘어갈 준비가 됐습니다. 첫째, I_Product 뷰와의 관계 — S/4HANA는 Material과 Product를 시맨틱적으로 통합하는 방향으로 발전하고 있어 두 뷰의 차이를 이해해 두면 좋습니다. 둘째, RAP(RESTful Application Programming Model) 기반의 Behavior Definition을 얹어 자재 마스터를 조회·변경하는 Fiori 앱을 직접 만들어 볼 수 있습니다. 셋째, CDS Analytical View를 활용해 자재별 재고·원가 분석을 Embedded Analytics로 확장하는 흐름도 자연스럽습니다. 마지막으로, ABAP Cloud 개발 모델에서는 릴리즈되지 않은 오브젝트 사용이 원천 차단되므로 뷰 릴리즈 상태 관리 정책도 함께 학습해 두면 좋습니다.
더 읽어볼 자료
- SAP Help Portal — Virtual Data Model (VDM) in SAP S/4HANA
- SAP Help Portal — Core Data Services (CDS) Overview
- SAP Help Portal — S/4HANA Product Master (Material) Documentation
- SAP Help Portal — ABAP SQL SELECT Reference
- SAP Community — ABAP CDS Views in S/4HANA
- SAP Business Accelerator Hub — S/4HANA Cloud OData 카탈로그
댓글 0
아직 댓글이 없습니다.