ABAP

EBAN 직접 조회, 80%가 모르는 CDS 뷰 차이 #shorts #SAP #ABAP

▶ YouTube에서 보기

이 예제로 얻는 것

SAP S/4HANA 환경에서 구매 요청(Purchase Requisition) 데이터를 조회할 때, 전통적으로는 EBAN 테이블을 직접 SELECT 해왔습니다. 하지만 ABAP Cloud, RAP, Fiori Elements 시대에는 I_PurchaseRequisition CDS 뷰를 사용하는 방식이 권장됩니다. 이 글은 EBAN에 익숙한 개발자가 I_PurchaseRequisition으로 부드럽게 전환할 수 있도록 개념·비교·실전 코드·마이그레이션 체크까지 다룹니다.

  • VDM(Virtual Data Model) 계층에서 I_ 뷰의 역할 이해
  • EBAN 필드 → CDS 시맨틱 필드 매핑 습득
  • Association을 활용한 조인 없는 관련 데이터 접근
  • ZC_ 소비 뷰(Consumption View) 작성 실습
  • ABAP Unit + cl_cds_test_environment로 CDS 뷰 테스트

미리 알아두면 좋은 것

OpenSQL / ABAP SQL 기본 문법, Core Data Services(CDS) 뷰의 기초 개념(@AbapCatalog.sqlViewName, define view), 구매(MM) 모듈의 구매 요청(PR) 개념(BANFN, BNFPO), Eclipse ADT(ABAP Development Tools) 사용 경험이 있으면 이해가 훨씬 빠릅니다. RAP나 Fiori Elements 경험은 필수는 아니지만 도움이 됩니다.

실습 환경과 준비물

  • SAP S/4HANA 2022 이상 (온프레미스) 또는 S/4HANA Cloud Public Edition 최신 릴리즈
  • ABAP Platform 2022 이상 (VDM 뷰 I_PurchaseRequisition이 안정적으로 제공되는 기준)
  • ABAP Development Tools (ADT) for Eclipse 최신 버전
  • 권한: S_DEVELOP (개발), MM 모듈 데이터 조회 권한(M_BANF_*)
  • 테스트 데이터: 구매 요청 몇 건이 등록된 클라이언트(MM01/ME51N 등)

온프레미스에서는 SE11에서 EBAN 테이블 정의를, ADT에서 I_PurchaseRequisition을 열어 두 정의를 나란히 비교하면 이해가 빠릅니다. ABAP Cloud(Steampunk, 개발자 확장) 환경이라면 EBAN 직접 접근이 원천 차단되므로 CDS 뷰가 유일한 통로입니다.

핵심 개념: VDM 계층과 I_PurchaseRequisition의 위치

SAP의 VDM(Virtual Data Model)은 CDS 뷰를 3계층으로 분류합니다. 마치 데이터 시장의 도소매 구조와 비슷합니다.

  • Basic View (I_ 접두어): 원천 테이블에 가장 가까운 뷰. EBAN, EKKO 같은 물리 테이블을 시맨틱 필드명으로 감싸고, 표준 Association을 제공합니다. I_PurchaseRequisition이 여기 속합니다.
  • Composite View (C_ 접두어 - Composite / P_ 등): 여러 Basic View를 조합해 비즈니스 의미가 있는 데이터셋을 만듭니다.
  • Consumption View (C_ 접두어): UI(Fiori Elements), OData Service 등 최종 소비를 위한 뷰. UI 어노테이션이 붙습니다.

EBAN을 도매 창고, I_PurchaseRequisition을 표준 규격 상자로 재포장한 물류센터, C_PurchaseRequisitionTP류를 완제품 매장이라고 생각하면 됩니다. 개발자는 물류센터 단계인 I_ 뷰부터 접근하는 것이 원칙입니다.

왜 EBAN 직접 접근을 지양하는가

  • ABAP Cloud 호환성: Cloud Development Model에서는 릴리즈되지 않은 테이블 직접 접근이 금지됩니다. EBAN은 릴리즈 컨트랙트가 Not Released이지만, I_PurchaseRequisition은 Cloud Development(C1) 컨트랙트로 릴리즈되어 있습니다.
  • 시맨틱 필드명: BANFN 대신 PurchaseRequisition, MENGE 대신 RequestedQuantity처럼 코드만 봐도 의미가 파악됩니다.
  • Association: _Material, _Plant 같은 Association으로 JOIN을 직접 작성하지 않고 관련 엔티티에 접근합니다.
  • 업그레이드 안전성: SAP가 물리 테이블 구조를 조정해도 CDS 뷰가 호환 레이어를 제공합니다.

I_PurchaseRequisition의 주요 필드

CDS 필드EBAN 필드의미
PurchaseRequisitionBANFN구매 요청 번호
PurchaseRequisitionItemBNFPO구매 요청 아이템
MaterialMATNR자재 번호
PlantWERKS플랜트
RequestedQuantityMENGE요청 수량
PurchasingOrganizationEKORG구매 조직
AccountAssignmentCategoryKNTTP계정 지정 카테고리
PurReqReleaseStatusFRGZU릴리즈 상태
CreationDateBADAT생성일

1단계: 기본 조회 예제

가장 단순한 형태로 특정 플랜트의 미완료 구매 요청을 조회합니다. EBAN SELECT와 비교해 보면 필드명이 얼마나 읽기 쉬워졌는지 실감할 수 있습니다.

REPORT z_pr_basic_read.

DATA: lt_pr TYPE STANDARD TABLE OF i_purchaserequisition.

SELECT PurchaseRequisition,
       PurchaseRequisitionItem,
       Material,
       Plant,
       RequestedQuantity,
       BaseUnit,
       CreationDate,
       PurReqReleaseStatus
  FROM i_purchaserequisition
  WHERE Plant           = '1010'
    AND IsDeleted       = @abap_false
    AND PurchaseRequisitionIsClosed = @abap_false
  ORDER BY CreationDate DESCENDING
  INTO TABLE @lt_pr
  UP TO 100 ROWS.

LOOP AT lt_pr ASSIGNING FIELD-SYMBOL(<fs_pr>).
  WRITE: / <fs_pr>-PurchaseRequisition,
           <fs_pr>-PurchaseRequisitionItem,
           <fs_pr>-Material,
           <fs_pr>-RequestedQuantity,
           <fs_pr>-BaseUnit.
ENDLOOP.

동일 조회를 EBAN으로 작성한다면 WHERE WERKS = '1010' AND LOEKZ = '' AND ...처럼 되어 도메인 지식이 없으면 해석이 어렵습니다.

2단계: 실무 시나리오 - Association과 에러 처리

구매 요청 목록과 함께 자재의 설명, 플랜트 이름까지 한 번에 가져오는 시나리오입니다. Association 표기 _Material, _Plant를 활용하면 JOIN을 명시적으로 쓰지 않아도 됩니다.

CLASS zcl_pr_reader DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES:
      BEGIN OF ty_pr_enriched,
        pr_number     TYPE i_purchaserequisition-purchaserequisition,
        pr_item       TYPE i_purchaserequisition-purchaserequisitionitem,
        material      TYPE i_purchaserequisition-material,
        material_desc TYPE string,
        plant         TYPE i_purchaserequisition-plant,
        plant_name    TYPE string,
        quantity      TYPE i_purchaserequisition-requestedquantity,
        base_unit     TYPE i_purchaserequisition-baseunit,
      END OF ty_pr_enriched,
      tt_pr_enriched TYPE STANDARD TABLE OF ty_pr_enriched WITH EMPTY KEY.

    METHODS read_open_requisitions
      IMPORTING iv_plant        TYPE i_purchaserequisition-plant
      RETURNING VALUE(rt_result) TYPE tt_pr_enriched
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_pr_reader IMPLEMENTATION.
  METHOD read_open_requisitions.
    TRY.
        SELECT pr~PurchaseRequisition       AS pr_number,
               pr~PurchaseRequisitionItem   AS pr_item,
               pr~Material                  AS material,
               mat~MaterialName             AS material_desc,
               pr~Plant                     AS plant,
               plant~PlantName              AS plant_name,
               pr~RequestedQuantity         AS quantity,
               pr~BaseUnit                  AS base_unit
          FROM i_purchaserequisition AS pr
               LEFT OUTER JOIN i_product AS mat
                    ON  mat~Product = pr~Material
                    AND mat~Language = @sy-langu
               LEFT OUTER JOIN i_plant AS plant
                    ON plant~Plant = pr~Plant
          WHERE pr~Plant = @iv_plant
            AND pr~PurchaseRequisitionIsClosed = @abap_false
          INTO TABLE @rt_result.
      CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
        RAISE EXCEPTION lx_sql.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

3단계: 프로덕션 - ZC_ 소비 뷰와 단위 테스트

미승인(릴리즈 대기) 구매 요청만 필터링하는 사용자 정의 뷰를 만들고, cl_cds_test_environment로 단위 테스트를 작성합니다.

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: '미승인 구매 요청 소비 뷰'
@Metadata.allowExtensions: true
define view entity ZC_PurReqPendingRelease
  as select from I_PurchaseRequisition as PR

  association [0..1] to I_Product as _Material
    on $projection.Material = _Material.Product

  association [0..1] to I_Plant   as _Plant
    on $projection.Plant   = _Plant.Plant
{
  key PR.PurchaseRequisition,
  key PR.PurchaseRequisitionItem,
      PR.Material,
      PR.Plant,
      PR.RequestedQuantity,
      PR.BaseUnit,
      PR.PurchasingGroup,
      PR.PurchasingOrganization,
      PR.AccountAssignmentCategory,
      PR.CreationDate,
      PR.PurReqReleaseStatus,

      _Material,
      _Plant
}
where PR.PurReqReleaseStatus         = ''
  and PR.PurchaseRequisitionIsClosed = false
  and PR.IsDeleted                   = false;
CLASS ltcl_pr_pending DEFINITION FINAL FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    CLASS-DATA env TYPE REF TO if_cds_test_environment.

    CLASS-METHODS class_setup.
    CLASS-METHODS class_teardown.
    METHODS setup.
    METHODS pending_only FOR TESTING RAISING cx_static_check.
ENDCLASS.

CLASS ltcl_pr_pending IMPLEMENTATION.
  METHOD class_setup.
    env = cl_cds_test_environment=>create(
            i_for_entity = 'ZC_PURREQPENDINGRELEASE' ).
  ENDMETHOD.

  METHOD class_teardown.
    env->destroy( ).
  ENDMETHOD.

  METHOD setup.
    env->clear_doubles( ).
  ENDMETHOD.

  METHOD pending_only.
    DATA lt_double TYPE STANDARD TABLE OF i_purchaserequisition.

    lt_double = VALUE #(
      ( PurchaseRequisition = '1000001' PurchaseRequisitionItem = '10'
        Plant = '1010' Material = 'MAT-A' RequestedQuantity = '5'
        PurReqReleaseStatus = '' PurchaseRequisitionIsClosed = abap_false
        IsDeleted = abap_false )
      ( PurchaseRequisition = '1000002' PurchaseRequisitionItem = '10'
        Plant = '1010' Material = 'MAT-B' RequestedQuantity = '2'
        PurReqReleaseStatus = 'X' PurchaseRequisitionIsClosed = abap_false
        IsDeleted = abap_false )
    ).
    env->insert_test_data( lt_double ).

    SELECT * FROM ZC_PurReqPendingRelease INTO TABLE @DATA(lt_actual).

    cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_actual ) ).
    cl_abap_unit_assert=>assert_equals( exp = '1000001'
                                       act = lt_actual[ 1 ]-PurchaseRequisition ).
  ENDMETHOD.
ENDCLASS.

자주 겪는 실수와 해결

Q1. EBAN에는 있는데 I_PurchaseRequisition에는 필드가 안 보입니다.
CDS 뷰는 시맨틱 필드만 노출합니다. EBAN의 LOEKZ(삭제 마크)는 IsDeleted로, FRGZUPurReqReleaseStatus로 이름이 바뀝니다. ADT에서 뷰를 열어 Field 탭을 확인하거나 F2로 원소스 매핑을 조회하세요.

Q2. Association 경로가 헷갈립니다.
Association 이름은 뷰 정의에서 association ... as _Material로 선언된 별칭 그대로 씁니다. ABAP SQL에서는 pr~\_Material.MaterialName 형태의 경로 표현식으로 접근합니다.

Q3. 대량 조회 시 성능이 EBAN 직접 SELECT보다 느립니다.
I_ 뷰는 여러 Association·계산 필드를 포함하므로 필요한 필드만 명시적으로 SELECT 하는 것이 중요합니다. SELECT *는 피하고, WHERE 조건을 인덱스가 있는 필드(PurchaseRequisition, Plant 등)로 좁혀야 합니다.

Q4. ABAP Cloud에서 EBAN을 직접 SELECT 했더니 신택스 에러가 납니다.
EBAN은 Cloud Development 컨트랙트로 릴리즈되어 있지 않기 때문에 정상 동작입니다. I_PurchaseRequisition이나 API 릴리즈된 소비 뷰만 사용해야 합니다.

마이그레이션 체크리스트

EBAN 직접 조회를 I_PurchaseRequisition으로 교체할 때 놓치기 쉬운 항목을 정리합니다.

  • BANFNPurchaseRequisition, BNFPOPurchaseRequisitionItem
  • MATNRMaterial, WERKSPlant, MENGERequestedQuantity
  • LOEKZ = ''IsDeleted = abap_false
  • FRGZUPurReqReleaseStatus (릴리즈 상태 boolean화)
  • MARA 조인(자재명) → _Material Association 경로
  • ABAP Unit: cl_cds_test_environment로 ZC_ 뷰 WHERE 조건 격리 검증
  • 병렬 결과 비교: 기존 EBAN 쿼리 행 수 vs CDS 뷰 결과 행 수 일치 확인

댓글 0

아직 댓글이 없습니다.