ABAP

MARA 직접 SELECT 금지 — VDM 전환 3가지 이유 #shorts #SAP #ABAP

I_Product CDS View란 무엇인가

SAP의 Virtual Data Model(VDM)에서 I_Product는 제품 마스터 데이터를 표준화된 방식으로 노출하는 핵심 인터페이스 CDS View입니다. ABAP CDS(Core Data Services)는 데이터베이스 수준에서 뷰를 정의하고 재사용할 수 있게 해주는 프레임워크로, SAP S/4HANA에서는 전통적인 테이블 직접 접근 대신 이 VDM 계층을 통해 데이터에 접근하는 것이 권장됩니다.

I_Product의 "I_" 접두어는 "Interface"를 의미하며, 외부 소비를 위한 안정적인 공개 API 역할을 합니다. 이 뷰는 내부적으로 MARA(제품 마스터 일반 데이터)를 중심으로 여러 관련 테이블들을 조인하여 제품에 관한 통합적인 뷰를 제공합니다.

VDM 계층 구조

SAP VDM은 다음 세 가지 계층으로 구성됩니다:

계층 접두어 역할 예시
기본 뷰 (Basic View) I_ 단일 엔티티 표현, 재사용 가능한 빌딩 블록 I_Product, I_Plant
복합 뷰 (Composite View) I_ (복합) 여러 기본 뷰 조합, 도메인 로직 포함 I_ProductPlant
소비 뷰 (Consumption View) C_ 앱/서비스별 맞춤형 뷰, UI/API 노출용 C_ProductStockSalesDelivery

I_Product는 기본 뷰이면서 동시에 인터페이스 역할을 하므로, 상위 뷰들이 이 뷰에 안정적으로 의존할 수 있습니다. SAP는 I_ 뷰의 API를 버전 간에 유지하므로, 커스텀 개발 시 MARA를 직접 사용하는 것보다 안정적입니다.

I_Product가 조인하는 테이블 구조

I_Product는 MARA를 루트 테이블로 하여 다음 테이블들을 조인합니다. 각 테이블이 어떤 정보를 제공하는지 이해하면 필요한 필드를 효율적으로 선택할 수 있습니다.

핵심 조인 테이블 목록

테이블 설명 조인 키 주요 필드
MARA 제품 마스터 일반 데이터 (루트) MATNR MTART, MATKL, MEINS, BRGEW, NTGEW
MAKT 제품 설명 (언어별) MATNR + SPRAS MAKTX (단축 설명), MAKTG (긴 설명)
MARC 플랜트별 제품 데이터 MATNR + WERKS PSTAT, MMSTA, DISMM, DZEIT, EISBE
MBEW 재고 평가 데이터 MATNR + BWKEY STPRS, VERPR, PEINH, BKLAS, VPRSV
MVKE 판매 조직별 제품 데이터 MATNR + VKORG + VTWEG MTPOS, KONDM, PROVG, KTGRM
MLAN 세금 분류 데이터 MATNR + ALAND + TATY1 TAXM1 (세금 분류 코드)
T001W 플랜트 마스터 WERKS NAME1, ORT01, LAND1

I_Product CDS View DDL 구조 예시

실제 SAP 시스템의 I_Product는 내부적으로 다음과 유사한 구조를 가집니다. (실제 구현은 SAP 버전에 따라 다를 수 있으며, 아래는 개념 설명을 위한 예시입니다.)

@AbapCatalog.sqlViewName: 'IPRODUCT'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Product - Basic Data'
@VDM.viewType: #BASIC
@ObjectModel.usageType:{
  serviceQuality: #A,
  sizeCategory: #XL,
  dataClass: #MASTER
}

define view I_Product
  as select from mara

  association [0..*] to I_ProductText        as _ProductText
    on $projection.Product = _ProductText.Product

  association [0..*] to I_ProductPlant       as _ProductPlant
    on $projection.Product = _ProductPlant.Product

  association [0..*] to I_ProductValuationJA as _ProductValuation
    on $projection.Product = _ProductValuation.Product

{
  key mara.matnr                   as Product,
      mara.mtart                   as ProductType,
      mara.matkl                   as ProductGroup,
      mara.meins                   as BaseUnit,
      mara.brgew                   as GrossWeight,
      mara.ntgew                   as NetWeight,
      mara.gewei                   as WeightUnit,
      mara.volum                   as Volume,
      mara.voleh                   as VolumeUnit,
      mara.ean11                   as InternationalArticleNumber,
      mara.mstae                   as CrossPlantProductStatus,
      mara.mstde                   as CrossPlantProductStatusValidFrom,
      mara.normt                   as IndustrySector,
      mara.laeng                   as ProductLength,
      mara.breit                   as ProductWidth,
      mara.hoehe                   as ProductHeight,
      mara.meabm                   as SizeOrDimensionUnit,
      mara.ernam                   as CreatedByUser,
      mara.ersda                   as CreationDate,
      mara.aenam                   as LastChangedByUser,
      mara.aedat                   as LastChangeDate,

      /* Associations */
      _ProductText,
      _ProductPlant,
      _ProductValuation
}

I_ProductText 뷰 (MAKT 기반)

@AbapCatalog.sqlViewName: 'IPRODUCTTEXT'
@VDM.viewType: #BASIC
@ObjectModel.dataCategory: #TEXT

define view I_ProductText
  as select from makt
{
  key matnr  as Product,
  key spras  as Language,
      maktx  as ProductDescription,
      maktg  as ProductExternalDescription
}

ABAP에서 I_Product 활용하기

I_Product를 활용하는 방법은 크게 세 가지입니다: Open SQL로 직접 SELECT, CDS Association을 통한 관련 데이터 조회, ABAP RESTful Application Programming(RAP) 모델에서의 활용.

예시 1: 기본 제품 정보 조회

*&---------------------------------------------------------------------*
*& 기본 제품 정보 조회 - I_Product 직접 SELECT
*&---------------------------------------------------------------------*
DATA: lt_products TYPE TABLE OF ZPRODUCT_INFO,
      ls_product  LIKE LINE OF lt_products.

DATA: BEGIN OF ls_result,
        product              TYPE i_product-product,
        producttype          TYPE i_product-producttype,
        productgroup         TYPE i_product-productgroup,
        baseunit             TYPE i_product-baseunit,
        grossweight          TYPE i_product-grossweight,
        weightunit           TYPE i_product-weightunit,
        lastchangedate       TYPE i_product-lastchangedate,
      END OF ls_result.
DATA: lt_results LIKE TABLE OF ls_result.

SELECT product,
       producttype,
       productgroup,
       baseunit,
       grossweight,
       weightunit,
       lastchangedate
  FROM i_product
  WHERE producttype = 'FERT'        " 완성품만
    AND crossplantproductstatus = '' " 유효한 제품만
  ORDER BY product
  INTO TABLE @lt_results.

LOOP AT lt_results INTO ls_result.
  WRITE: / ls_result-product,
           ls_result-producttype,
           ls_result-baseunit,
           ls_result-grossweight,
           ls_result-weightunit.
ENDLOOP.

예시 2: 제품 설명 포함 조회 (Association 활용)

*&---------------------------------------------------------------------*
*& 제품 + 설명 조인 조회 - Association 활용
*& I_ProductText는 I_Product의 _ProductText Association
*&---------------------------------------------------------------------*
DATA: BEGIN OF ls_product_with_text,
        product             TYPE c LENGTH 40,
        producttype         TYPE c LENGTH 4,
        productgroup        TYPE c LENGTH 9,
        productdescription  TYPE c LENGTH 40,
        language            TYPE c LENGTH 2,
      END OF ls_product_with_text.
DATA: lt_products_text LIKE TABLE OF ls_product_with_text.

" Association을 통한 텍스트 조인 (언어별)
SELECT p~product,
       p~producttype,
       p~productgroup,
       t~productdescription,
       t~language
  FROM i_product AS p
  JOIN i_producttext AS t
    ON p~product = t~product
   AND t~language = @sy-langu
  WHERE p~productgroup IN ('MAT01', 'MAT02', 'MAT03')
    AND p~producttype  <> 'DIEN'  " 서비스 제품 제외
  ORDER BY p~product
  INTO TABLE @lt_products_text.

IF sy-subrc = 0.
  LOOP AT lt_products_text INTO ls_product_with_text.
    WRITE: / ls_product_with_text-product,
               ls_product_with_text-productdescription.
  ENDLOOP.
ELSE.
  MESSAGE '조회된 제품이 없습니다.' TYPE 'I'.
ENDIF.

예시 3: 재고 평가 데이터와 결합 조회

*&---------------------------------------------------------------------*
*& 제품 + 재고평가(MBEW) 통합 조회
*& 구매 표준가 또는 이동평균가 확인
*&---------------------------------------------------------------------*
TYPES: BEGIN OF ty_product_valuation,
         product          TYPE mara-matnr,
         productdesc      TYPE makt-maktx,
         valuationarea    TYPE mbew-bwkey,
         pricectrlind     TYPE mbew-vprsv,  " S=표준가, V=이동평균가
         standardprice    TYPE mbew-stprs,
         movavgprice      TYPE mbew-verpr,
         priceunit        TYPE mbew-peinh,
         valuationclass   TYPE mbew-bklas,
       END OF ty_product_valuation.

DATA: lt_val TYPE TABLE OF ty_product_valuation.

" MBEW 기반 I_ProductValuation 뷰 활용
" (실제 시스템에서는 I_ProductValuationJA 또는 유사 뷰 사용)
SELECT p~matnr      AS product,
       t~maktx      AS productdesc,
       v~bwkey      AS valuationarea,
       v~vprsv      AS pricectrlind,
       v~stprs      AS standardprice,
       v~verpr      AS movavgprice,
       v~peinh      AS priceunit,
       v~bklas      AS valuationclass
  FROM mara AS p
  JOIN makt AS t ON p~matnr = t~matnr AND t~spras = @sy-langu
  JOIN mbew AS v ON p~matnr = v~matnr AND v~bwkey = @lv_plant
  WHERE p~mtart = 'ROH'              " 원자재만
    AND v~vprsv = 'S'               " 표준가 관리 품목만
    AND v~stprs > 0                 " 표준가 등록된 품목만
  ORDER BY p~matnr
  INTO TABLE @lt_val.

" 조회 결과 처리
LOOP AT lt_val INTO DATA(ls_val).
  " 표준가와 이동평균가 비교 분석 로직
  IF ls_val-pricectrlind = 'S'.
    WRITE: / ls_val-product, '표준가:', ls_val-standardprice, ls_val-priceunit.
  ENDIF.
ENDLOOP.

실무 활용 시나리오

시나리오 1: 제품 마스터 검증 리포트

신규 제품 등록 후 필수 데이터 완성도 검증이 필요한 경우, I_Product와 관련 뷰를 활용하여 누락 데이터를 확인할 수 있습니다.

*&---------------------------------------------------------------------*
*& 제품 마스터 데이터 완성도 검증
*& - 설명 누락, 중량 미입력, 제품군 미지정 등 체크
*&---------------------------------------------------------------------*
DATA: BEGIN OF ls_incomplete,
        product      TYPE mara-matnr,
        issue_type   TYPE c LENGTH 50,
      END OF ls_incomplete.
DATA: lt_incomplete LIKE TABLE OF ls_incomplete.

" 설명 누락 제품 확인
SELECT p~product
  FROM i_product AS p
  WHERE NOT EXISTS (
    SELECT 1 FROM i_producttext AS t
    WHERE t~product  = p~product
      AND t~language = 'KO'
  )
  AND p~lastchangedate > @lv_from_date
  INTO TABLE @DATA(lt_no_desc).

LOOP AT lt_no_desc INTO DATA(ls_prod).
  ls_incomplete-product    = ls_prod-product.
  ls_incomplete-issue_type = '한국어 제품 설명 누락'.
  APPEND ls_incomplete TO lt_incomplete.
ENDLOOP.

" 중량 미입력 완성품 확인
SELECT product
  FROM i_product
  WHERE producttype = 'FERT'
    AND ( grossweight = 0 OR netweight = 0 )
  INTO TABLE @DATA(lt_no_weight).

LOOP AT lt_no_weight INTO DATA(ls_w).
  ls_incomplete-product    = ls_w-product.
  ls_incomplete-issue_type = '완성품 중량 정보 미입력'.
  APPEND ls_incomplete TO lt_incomplete.
ENDLOOP.

" 결과 출력
LOOP AT lt_incomplete INTO ls_incomplete.
  WRITE: / ls_incomplete-product, ls_incomplete-issue_type.
ENDLOOP.

시나리오 2: OData 서비스에서 I_Product 노출

RAP(RESTful ABAP Programming) 모델을 사용하면 I_Product를 기반으로 OData V4 서비스를 빠르게 구성할 수 있습니다. 소비 뷰(C_)를 생성할 때 I_Product를 기반으로 필요한 필드만 선택하는 방식을 권장합니다.

@AbapCatalog.sqlViewName: 'ZPRODCATALOG'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: '제품 카탈로그 소비 뷰'
@VDM.viewType: #CONSUMPTION
@OData.publish: true

define view ZC_ProductCatalog
  as select from I_Product as prod

  association [0..*] to I_ProductText as _text
    on  $projection.Product = _text.Product
    and _text.Language       = $session.system_language

{
  key prod.Product,
      prod.ProductType,
      prod.ProductGroup,
      prod.BaseUnit,
      prod.GrossWeight,
      prod.NetWeight,
      prod.WeightUnit,
      prod.LastChangeDate,
      _text.ProductDescription,

      /* Navigation */
      _text
}
where prod.ProductType <> 'DIEN'

성능 고려사항과 주의점

필드 선택 최적화

I_Product는 내부적으로 여러 테이블을 조인하므로, SELECT * 사용은 피하고 필요한 필드만 명시적으로 선택해야 합니다. 특히 MAKT(텍스트), MBEW(평가), MVKE(판매) 데이터가 필요 없는 경우, 해당 Association을 통해 접근하지 않으면 조인이 발생하지 않습니다.

  • 필요한 필드만 SELECT 절에 명시 (SELECT *는 지양)
  • WHERE 절에 인덱스 필드(MATNR, MTART, MATKL) 활용
  • 텍스트 조인 시 반드시 언어 조건(SPRAS = sy-langu) 추가
  • 대량 조회 시 PACKAGE SIZE와 함께 CURSOR 방식 고려
  • CDS View에 정의된 @AccessControl을 통해 권한 자동 적용됨을 인지

접근 권한 (Authorization Check)

I_Product@AccessControl.authorizationCheck: #CHECK 설정으로 CDS Access Control(DCL)을 통해 권한을 자동으로 검증합니다. 즉, 사용자가 접근 권한이 없는 제품 데이터는 자동으로 필터링되므로, 별도의 권한 체크 로직을 추가할 필요가 없습니다. 이것이 MARA 직접 접근 대신 I_Product를 사용해야 하는 중요한 이유 중 하나입니다.

커스텀 개발 시 권장 패턴

  • 확장(Extension): I_Product를 직접 수정하지 말고, 커스텀 소비 뷰(ZC_)를 생성하여 래핑
  • Association 활용: 필요한 관련 데이터는 Association으로 Lazy Loading 방식 접근
  • 캐싱 고려: 반복 조회되는 제품 마스터는 버퍼링 또는 인메모리 테이블 활용
  • 버전 안정성: I_ 뷰는 SAP가 하위 호환성을 보장하므로 업그레이드 영향 최소화

MARA 직접 접근 vs I_Product 사용 비교

구분 MARA 직접 접근 I_Product 활용
권한 체크 수동으로 AUTHORITY-CHECK 필요 DCL에 의해 자동 적용
업그레이드 안정성 테이블 구조 변경 시 영향받음 SAP가 API 호환성 보장
데이터 통합 관련 테이블 수동 조인 필요 Association으로 선언적 조인
성능 최적화된 직접 쿼리 가능 VDM 오버헤드 있으나 HANA 최적화
유지보수 변경 시 모든 참조 코드 수정 필요 중앙 집중식 변경 반영
표준 준수 비권장 (레거시 방식) SAP S/4HANA 표준 방식

SAP S/4HANA 환경에서는 I_Product를 통한 접근이 권장되는 표준 방식입니다. MARA를 직접 참조하는 레거시 코드가 있다면, 점진적으로 I_Product 기반으로 전환하는 것이 장기적인 유지보수성과 시스템 업그레이드 안정성 측면에서 유리합니다.

댓글 0

아직 댓글이 없습니다.