ABAP

AMDP vs CDS Table Function 선택 기준 #shorts #SAP #ABAP

이 글에서 다루는 내용

SAP HANA 기반 시스템에서 복잡한 계산 로직을 데이터베이스로 내리는(Code Pushdown) 대표 수단이 CDS Table Function과 AMDP(ABAP Managed Database Procedure)입니다. 두 기술 모두 내부적으로 SQLScript를 사용하지만, 소비 방식과 유지보수 특성이 크게 다르기 때문에 "언제 어떤 것을 쓰는가"가 실무에서 자주 논쟁이 됩니다. 이 글은 창고 재고 회전율 분석이라는 커스텀 물류 시나리오를 기준으로 두 기술을 비교합니다.

  • CDS Table Function과 AMDP의 내부 동작 원리를 구분해 설명할 수 있다
  • 기본 AMDP 프로시저와 Table Function 구현 코드를 직접 작성할 수 있다
  • 성능·재사용성·테스트 관점에서 선택 기준을 세울 수 있다
  • 클라이언트 처리, 필드 불일치 등 대표적인 활성화 오류를 스스로 해결할 수 있다

미리 갖추면 좋은 배경

ABAP Open SQL과 CDS 뷰의 기본 문법(define view entity, annotation)을 이해하고 있어야 합니다. SQLScript는 처음이어도 따라올 수 있도록 예제마다 설명을 붙였지만, SELECT 문 기반의 집계(GROUP BY, JOIN) 경험이 있으면 훨씬 수월합니다. ADT(ABAP Development Tools in Eclipse) 사용 경험도 필요합니다. 두 기술 모두 SE80/SE38 같은 GUI 기반 도구에서는 편집할 수 없습니다.

실습 환경과 버전 요구사항

두 기술 모두 데이터베이스가 SAP HANA일 때만 동작합니다. 일반적으로 다음 조건이 필요합니다.

  • AMDP: SAP NetWeaver AS ABAP 7.40 SP05 이상 + SAP HANA DB. S/4HANA(온프레미스/클라우드) 전 버전에서 사용 가능
  • CDS Table Function: ABAP 7.50 이상. 구현체가 AMDP 메서드이므로 AMDP 조건을 함께 충족해야 함
  • 개발 도구: ADT(Eclipse) 필수. CDS 소스와 AMDP 클래스 모두 ADT에서만 작성/활성화 가능
  • ABAP Cloud(BTP ABAP Environment, S/4HANA Cloud Public Edition): 두 기술 모두 릴리스된 범위 내에서 사용 가능하나, USING 절에 지정하는 오브젝트가 릴리스 계약을 만족해야 함

이 글의 예제는 S/4HANA 2023(ABAP 7.58) 기준으로 작성했으며, 커스텀 테이블 ztlx_stockmov(자재 이동 이력)와 ztlx_matstock(자재별 평균 재고)을 사용합니다.

두 기술의 동작 원리와 선택 기준

비유하자면 AMDP는 "데이터베이스 안에 두는 개인 작업실"이고, CDS Table Function은 "데이터 모델이라는 건물에 정식 입주한 사무실"입니다. 작업실(AMDP)에서는 무엇이든 자유롭게 할 수 있지만 결과물을 밖에서 쓰려면 직접 들고 나와야 하고, 사무실(Table Function)은 건물 규칙(CDS 뷰 스택)을 따라야 하지만 다른 입주자(다른 CDS 뷰, Open SQL, 분석 도구)가 언제든 방문할 수 있습니다.

AMDP의 동작 원리: AMDP는 ABAP 클래스 메서드 형태로 작성된 SQLScript 프로시저입니다. 소스는 ABAP 리포지토리에 저장되고, 최초 호출 시점에 HANA DB에 프로시저(또는 함수)로 자동 배포됩니다. 즉 수명주기(전송, 버전 관리)는 ABAP이 관리하고 실행은 HANA가 담당합니다. 여러 개의 출력 파라미터, 다단계 중간 결과, 스크립트 변수, 조건 분기 등 절차적 로직이 자유롭습니다.

CDS Table Function의 동작 원리: CDS 소스에서 함수 시그니처(파라미터와 반환 구조)를 선언하고, 실제 로직은 AMDP 클래스의 FOR TABLE FUNCTION 메서드로 구현합니다. 활성화되면 HANA의 테이블 함수로 배포되어, 일반 CDS 뷰처럼 Open SQL에서 SELECT하거나 다른 CDS 뷰의 데이터 소스로 조합할 수 있습니다. 반환은 항상 단일 테이블 형태이며 읽기 전용입니다.

관점CDS Table FunctionAMDP 프로시저
소비 방식Open SQL SELECT, 다른 CDS 뷰의 소스로 사용ABAP 메서드 호출로만 사용
반환 형태단일 테이블, 읽기 전용테이블/스칼라 다중 출력 가능
클라이언트 처리어노테이션 기반으로 프레임워크가 지원WHERE 조건으로 직접 처리
절차적 로직구현부는 자유이나 결과 계약이 고정루프·분기·중간 테이블 등 제약 적음
테스트CDS Test Double Framework로 스텁 처리 용이실 DB 의존 테스트가 되기 쉬움
재사용/확장뷰 스택·분석 쿼리·권한(DCL)과 자연스럽게 결합호출 지점마다 ABAP 코드 필요

실무 선택 기준을 한 문장으로 요약하면 이렇습니다. 결과를 데이터 모델의 일부로 재사용해야 하면 Table Function, 결과가 특정 ABAP 처리 단계 안에서만 소비되는 복잡한 계산이면 AMDP가 일반적으로 유리합니다. 성능 자체는 두 방식 모두 동일한 SQLScript 엔진에서 실행되므로 큰 차이가 없고, 차이를 만드는 것은 로직 구조와 유지보수 비용입니다.

실전 코드로 보는 3단계 구현

1단계 — 기본 AMDP: 출고 수량 집계

먼저 특정 플랜트의 자재별 출고 수량을 집계하는 순수 AMDP 프로시저입니다. IF_AMDP_MARKER_HDB 인터페이스와 USING 절이 핵심입니다.

CLASS zcl_lx_mov_agg DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_amdp_marker_hdb.
    TYPES: BEGIN OF ty_issue_sum,
             material_id TYPE matnr,
             total_qty   TYPE menge_d,
           END OF ty_issue_sum,
           tt_issue_sum TYPE STANDARD TABLE OF ty_issue_sum
                        WITH EMPTY KEY.
    CLASS-METHODS aggregate_issues
      IMPORTING VALUE(iv_client) TYPE mandt
                VALUE(iv_plant)  TYPE werks_d
      EXPORTING VALUE(et_result) TYPE tt_issue_sum.
ENDCLASS.

CLASS zcl_lx_mov_agg IMPLEMENTATION.
  METHOD aggregate_issues BY DATABASE PROCEDURE FOR HDB
                          LANGUAGE SQLSCRIPT
                          OPTIONS READ-ONLY
                          USING ztlx_stockmov.
    et_result = SELECT material_id,
                       SUM( mov_qty ) AS total_qty
                  FROM ztlx_stockmov
                 WHERE mandt    = :iv_client
                   AND plant_id = :iv_plant
                   AND mov_dir  = 'OUT'
                 GROUP BY material_id;
  ENDMETHOD.
ENDCLASS.

주의할 점은 mandt = :iv_client처럼 클라이언트 필터를 직접 넣어야 한다는 것입니다. AMDP 프로시저 내부는 Open SQL이 아니므로 자동 클라이언트 처리가 없습니다. 호출부에서는 sy-mandt를 넘겨줍니다.

2단계 — CDS Table Function: 재사용 가능한 회전율 계산

회전율(출고량 ÷ 평균 재고)을 여러 소비처(리포트, 분석 앱, 다른 뷰)에서 써야 한다면 Table Function이 적합합니다. 먼저 CDS 정의입니다.

@EndUserText.label: '자재 회전율 분석 함수'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@ClientHandling.type: #CLIENT_DEPENDENT
define table function ZLX_TF_STOCK_TURNOVER
  with parameters
    p_plant     : werks_d,
    p_from_date : abap.dats
returns
{
  client        : abap.clnt;
  material_id   : matnr;
  plant_id      : werks_d;
  total_issue   : abap.dec(15,3);
  avg_stock     : abap.dec(15,3);
  turnover_rate : abap.dec(9,2);
}
implemented by method
  zcl_lx_stock_analytics=>get_turnover;

구현 클래스에서는 이동 이력과 평균 재고를 조인하고, 0으로 나누는 상황을 방어합니다. Table Function 자체에는 예외 처리 구문이 없으므로, 데이터 품질 문제는 SQL 레벨에서 CASE 식으로 흡수하고 호출부 ABAP에서 cx_amdp_execution_error 계열 예외를 잡아 로깅하는 구조가 일반적입니다.

CLASS zcl_lx_stock_analytics DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_amdp_marker_hdb.
    CLASS-METHODS get_turnover
      FOR TABLE FUNCTION zlx_tf_stock_turnover.
ENDCLASS.

CLASS zcl_lx_stock_analytics IMPLEMENTATION.
  METHOD get_turnover BY DATABASE FUNCTION FOR HDB
                      LANGUAGE SQLSCRIPT
                      OPTIONS READ-ONLY
                      USING ztlx_stockmov ztlx_matstock.
    RETURN
      SELECT mov.mandt        AS client,
             mov.material_id,
             mov.plant_id,
             SUM( mov.mov_qty )            AS total_issue,
             AVG( stk.stock_qty )          AS avg_stock,
             CASE WHEN AVG( stk.stock_qty ) > 0
                  THEN SUM( mov.mov_qty ) / AVG( stk.stock_qty )
                  ELSE 0 END               AS turnover_rate
        FROM ztlx_stockmov AS mov
        INNER JOIN ztlx_matstock AS stk
          ON  stk.mandt       = mov.mandt
          AND stk.material_id = mov.material_id
          AND stk.plant_id    = mov.plant_id
       WHERE mov.mandt    = SESSION_CONTEXT('CDS_CLIENT')
         AND mov.plant_id = :p_plant
         AND mov.mov_date >= :p_from_date
         AND mov.mov_dir  = 'OUT'
       GROUP BY mov.mandt, mov.material_id, mov.plant_id;
  ENDMETHOD.
ENDCLASS.

호출부는 일반 CDS 뷰와 동일하게 Open SQL을 사용하며, 실패 시 애플리케이션 로그(BAL)나 자체 로그 테이블에 기록합니다.

TRY.
    SELECT * FROM zlx_tf_stock_turnover(
               p_plant = '1200', p_from_date = '20260101' )
      ORDER BY turnover_rate DESCENDING
      INTO TABLE @DATA(lt_turnover)
      UP TO 100 ROWS.
  CATCH cx_amdp_execution_failed INTO DATA(lx_err).
    " 로그 기록 후 사용자 친화적 메시지로 전환
    zcl_lx_applog=>log_error( lx_err->get_text( ) ).
ENDTRY.

3단계 — 프로덕션 수준: 권한·성능·테스트

운영 배포 전 세 가지를 보강합니다. 첫째, 권한입니다. Table Function 위에 소비용 뷰를 얹고 DCL로 플랜트 권한을 거는 패턴이 권장됩니다.

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: '회전율 소비 뷰'
define view entity ZLX_C_TURNOVER
  with parameters p_plant : werks_d, p_from_date : abap.dats
  as select from ZLX_TF_STOCK_TURNOVER(
       p_plant: $parameters.p_plant,
       p_from_date: $parameters.p_from_date )
{
  key material_id,
  key plant_id,
      total_issue,
      avg_stock,
      turnover_rate
}

둘째, 성능입니다. SQLScript 내부에서 스칼라 UDF 반복 호출이나 커서 루프를 쓰면 병렬화가 깨지므로, 가능한 한 위 예제처럼 선언적 SELECT 하나로 표현합니다. 실행 계획은 ADT의 SQL 콘솔에서 EXPLAIN PLAN 또는 PlanViz로 확인하고, 조인 키에 해당하는 필드가 대량 테이블에서 적절히 필터링되는지 점검합니다. 셋째, 테스트입니다. Table Function은 CDS Test Double Framework(cl_cds_test_environment)로 스텁 데이터를 주입해 소비 뷰 로직을 단위 테스트할 수 있는 반면, 순수 AMDP는 실 DB 데이터에 의존하는 테스트가 되기 쉽습니다. 테스트 용이성이 중요한 코어 로직이라면 이 차이만으로도 Table Function을 선택할 근거가 됩니다.

자주 겪는 문제와 해결 방법

Q1. 활성화 시 "필드 목록이 일치하지 않음" 오류가 납니다.
Table Function의 returns 목록과 SQLScript SELECT의 필드는 이름·순서·개수가 정확히 일치해야 합니다. SELECT *를 쓰면 테이블 구조 변경 시 바로 깨지므로, 항상 필드를 명시적으로 나열하고 별칭(AS)으로 이름을 맞추세요.

Q2. AMDP 실행 결과가 항상 비어 있습니다.
십중팔구 클라이언트 필터 누락 또는 잘못된 값입니다. AMDP 프로시저는 mandt를 자동 처리하지 않으므로 파라미터로 받은 클라이언트로 필터링해야 하고, Table Function은 @ClientHandling 어노테이션과 세션 컨텍스트 조합을 확인해야 합니다. 하드코딩된 '100' 같은 값이 남아 있지 않은지도 점검하세요.

Q3. "invalid object name" 런타임 오류가 발생합니다.
SQLScript에서 접근하는 모든 테이블/뷰/다른 AMDP를 USING 절에 선언했는지 확인하세요. 선언이 누락되면 배포 순서가 보장되지 않아 간헐적으로 실패합니다.

Q4. AMDP 내부를 디버깅하고 싶습니다.
ADT에서 AMDP 소스 라인에 브레이크포인트를 설정하면 SQLScript 디버거가 동작합니다(적절한 DB 권한 필요). 외부 브레이크포인트처럼 ABAP 디버거로는 내부에 진입할 수 없다는 점을 기억하세요.

Q5. HANA가 아닌 DB에서도 돌려야 한다면?
두 기술 모두 HANA 전용입니다. AnyDB 지원이 필요하면 CDS 뷰 + Open SQL로 표현 가능한 수준까지 로직을 단순화하거나, DB 종류별 분기 구현을 별도로 설계해야 합니다.

이어서 살펴볼 주제

이 글의 선택 기준이 잡혔다면, 다음 주제로 확장해 보세요. CDS 뷰 엔티티의 가상 요소(Virtual Element)와 ABAP 로직 결합, AMDP를 활용한 BW/4HANA 변환 루틴 최적화, SQLScript의 병렬 실행 힌트와 PlanViz 분석, 그리고 RAP 모델에서 Table Function을 조회 전용 엔티티로 활용하는 패턴이 자연스러운 다음 학습 경로입니다. ABAP Cloud 환경이라면 릴리스 계약(released API)과 Tier 개념도 함께 확인하는 것이 좋습니다.

댓글 0

아직 댓글이 없습니다.