1. CDS Table Function이 필요한 이유
일반 CDS View는 선언형 SQL 기반의 데이터 뷰라는 강점이 있지만, 실제 프로젝트에서 벽에 부딪히는 순간이 있습니다. 예를 들어 매출 데이터를 지역별로 집계하되, 특정 월에만 환율을 재계산해서 반영하고 싶다거나, 고객별 주문 이력을 재귀적으로 순회해서 상위 카테고리로 롤업해야 하는 요구사항입니다. 이런 로직은 순수 CDS DDL만으로는 표현이 어렵거나, 표현하더라도 유지보수가 힘든 복잡한 서브쿼리 뭉치가 됩니다.
- 일반 CDS View의 한계: 조건부 컬럼 계산, 동적 피벗, 재귀 CTE, 임시 테이블 활용 불가
- 기존 ABAP CDS Association만으로는 복잡한 절차형 로직 표현 불가
- HANA의 강점(Column Store, 파라미터화된 계산)을 CDS 레벨에서 활용하고 싶은 요구
이 글에서는 AMDP와 CDS Table Function이 어떤 역할 분담을 하는지, SQLScript 기반 절차형 로직을 CDS 계층에 노출하는 흐름, Input Parameter로 동적 필터를 걸어 재사용 가능한 뷰 설계, Fiori Elements에서 소비되는 OData 서비스로 연결, 운영 환경에서 발생하는 성능/트랜스포트 이슈 대응법까지 단계별로 다룹니다.
2. AMDP와 Table Function의 경계선 — 핵심 개념
CDS Table Function을 이해하려면 세 개의 층을 머릿속에 그려야 합니다. 가장 아래는 HANA 데이터베이스 안에서 실행되는 SQLScript입니다. 그 위는 ABAP이 관리하는 AMDP(ABAP Managed Database Procedure)로, ABAP 클래스 메서드 안에 SQLScript 코드를 담아 트랜스포트와 라이프사이클을 ABAP과 함께 관리합니다. 그리고 최상위에 CDS Table Function이 있는데, 이는 AMDP 메서드를 CDS 뷰처럼 노출하는 얇은 어댑터 역할을 합니다.
비유하자면 SQLScript는 주방장이 만드는 요리, AMDP는 그 요리를 담는 접시(ABAP 트랜스포트/버전 관리), Table Function은 손님(CDS 소비자)에게 내미는 메뉴판입니다. 손님은 메뉴판만 보고 주문할 뿐, 접시가 어떻게 만들어지는지 몰라도 됩니다.
동작 흐름을 단순화하면 이렇습니다.
- 개발자가 CDS Table Function DDL을 활성화하면 시스템이 HANA에 런타임 오브젝트를 생성
- 런타임 오브젝트는 AMDP 메서드를 호출하도록 링크됨
- 다른 CDS View나 Open SQL이 Table Function을 참조하면, HANA에서 SQLScript가 실행되고 결과 테이블이 반환
중요한 규칙 몇 가지가 있습니다. Table Function을 구현하는 AMDP 메서드는 반드시 BY DATABASE FUNCTION FOR HDB로 선언해야 합니다(일반 AMDP 프로시저인 BY DATABASE PROCEDURE와 다릅니다). 또한 반환 타입은 CDS Table Function에 선언된 구조와 정확히 일치해야 하며, 클라이언트 필드(client)를 포함시킬지 여부도 어노테이션으로 명시해야 합니다.
이 기능은 HANA 전용입니다. SAP NetWeaver AS ABAP 7.52 이상, SAP HANA 2.0 SPS05 이상 환경에서 동작하며, AnyDB에서는 Table Function이 실행되지 않습니다.
3. AMDP 클래스 설계 — 지역별 매출 집계 (RevenueByRegion)
먼저 CDS Table Function DDL을 정의합니다. ADT에서 New > Other > Core Data Services > Table Function으로 생성합니다.
@EndUserText.label: 'Revenue aggregated by region'
@ClientHandling.algorithm: #SESSION_VARIABLE
define table function ZTF_REVENUE_BY_REGION
with parameters
p_fiscal_year : abap.numc(4)
returns {
client : abap.clnt;
region_code : abap.char(4);
region_name : abap.char(40);
total_revenue : abap.dec(15,2);
order_count : abap.int4;
}
implemented by method
zcl_revenue_analytics=>get_by_region;
이제 AMDP 클래스를 만듭니다. 클래스 정의에서 if_amdp_marker_hdb 인터페이스 구현이 핵심입니다.
CLASS zcl_revenue_analytics DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_amdp_marker_hdb.
CLASS-METHODS get_by_region
FOR TABLE FUNCTION ztf_revenue_by_region.
ENDCLASS.
CLASS zcl_revenue_analytics IMPLEMENTATION.
METHOD get_by_region BY DATABASE FUNCTION
FOR HDB
LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING ztb_sales_ledger ztb_region.
RETURN
SELECT s.mandt AS client,
r.region_code AS region_code,
r.region_name AS region_name,
SUM( s.net_amount ) AS total_revenue,
COUNT( s.order_id ) AS order_count
FROM ztb_sales_ledger AS s
INNER JOIN ztb_region AS r
ON s.mandt = r.mandt
AND s.region_code = r.region_code
WHERE s.fiscal_year = :p_fiscal_year
AND s.mandt = SESSION_CONTEXT('CLIENT')
GROUP BY s.mandt, r.region_code, r.region_name;
ENDMETHOD.
ENDCLASS.
USING ztb_sales_ledger ztb_region 구문은 SQLScript 내에서 참조할 HANA 오브젝트를 명시합니다. 이 목록에 없는 테이블은 컴파일 오류가 납니다. 활성화 후 Open SQL에서 SELECT * FROM ztf_revenue_by_region( p_fiscal_year = '2026' )로 즉시 호출할 수 있습니다.
4. Input Parameter 활용 — 연도/지역 동적 필터와 YoY 계산
실무에서는 단일 파라미터 이상의 복합 필터와 계산이 필요합니다. 지역 범위, 최소 매출 임계값, 전년 대비 성장률(YoY)을 파라미터로 받아 처리하는 예제입니다.
define table function ZTF_REVENUE_RANKED
with parameters
p_fiscal_year : abap.numc(4),
p_region_from : abap.char(4),
p_region_to : abap.char(4),
p_min_amount : abap.dec(15,2)
returns {
client : abap.clnt;
region_code : abap.char(4);
rank_position : abap.int4;
total_revenue : abap.dec(15,2);
yoy_growth : abap.dec(9,4);
}
implemented by method
zcl_revenue_analytics=>get_ranked;
METHOD get_ranked BY DATABASE FUNCTION
FOR HDB LANGUAGE SQLSCRIPT
OPTIONS READ-ONLY
USING ztb_sales_ledger.
DECLARE lv_prev_year NVARCHAR(4);
lv_prev_year := LPAD( TO_VARCHAR( TO_INT( :p_fiscal_year ) - 1 ), 4, '0' );
lt_current =
SELECT mandt, region_code, SUM( net_amount ) AS amt
FROM ztb_sales_ledger
WHERE fiscal_year = :p_fiscal_year
AND region_code BETWEEN :p_region_from AND :p_region_to
GROUP BY mandt, region_code;
lt_prev =
SELECT mandt, region_code, SUM( net_amount ) AS amt
FROM ztb_sales_ledger
WHERE fiscal_year = :lv_prev_year
AND region_code BETWEEN :p_region_from AND :p_region_to
GROUP BY mandt, region_code;
RETURN
SELECT c.mandt AS client,
c.region_code,
RANK() OVER (ORDER BY c.amt DESC) AS rank_position,
c.amt AS total_revenue,
CASE WHEN p.amt IS NULL OR p.amt = 0 THEN 0
ELSE (c.amt - p.amt) / p.amt END AS yoy_growth
FROM :lt_current AS c
LEFT OUTER JOIN :lt_prev AS p
ON c.mandt = p.mandt AND c.region_code = p.region_code
WHERE c.amt >= :p_min_amount;
ENDMETHOD.
임시 테이블 변수 lt_current, lt_prev를 사용해 전년 동기 대비 성장률(YoY)을 계산했습니다. 순수 CDS View로는 이런 스텝바이스텝 계산이 매우 까다롭습니다. RANK() OVER (...) 윈도우 함수도 CDS DDL에서는 직접 지원하지 않지만 SQLScript에서는 자유롭게 사용할 수 있습니다.
성능 관점에서 중요한 원칙: Table Function은 결과 전체를 반환하므로, 상위 CDS View에서 WHERE를 걸어도 필터가 함수 내부까지 푸시다운되지 않을 수 있습니다. 필터 조건은 가능한 한 파라미터로 받아서 SQLScript 내부의 WHERE 절에 반영하는 편이 성능상 유리합니다.
5. Fiori Analytics 연결 — CDS 래퍼 뷰로 OData 노출
Table Function을 그대로 노출하기보다 상위 CDS View로 한 번 감싸는 편이 안전합니다. 접근 권한, 필드 라벨, Fiori 어노테이션을 상위 뷰에서 관리하기 위해서입니다.
@AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true
@OData.publish: true
define view entity ZC_RevenueRanking
with parameters
p_fiscal_year : abap.numc(4),
p_region_from : abap.char(4),
p_region_to : abap.char(4),
p_min_amount : abap.dec(15,2)
as select from ZTF_REVENUE_RANKED(
p_fiscal_year => :p_fiscal_year,
p_region_from => :p_region_from,
p_region_to => :p_region_to,
p_min_amount => :p_min_amount )
{
key region_code as RegionCode,
rank_position as RankPosition,
@Semantics.amount.currencyCode: 'CurrencyCode'
total_revenue as TotalRevenue,
cast( 'KRW' as abap.cuky ) as CurrencyCode,
yoy_growth as YoyGrowth
}
Fiori Elements SmartChart에 연결하려면 Metadata Extension으로 @UI.chart 어노테이션을 추가합니다.
@Metadata.layer: #CUSTOMER
annotate view ZC_RevenueRanking with
{
@UI.lineItem: [{ position: 10, label: '지역 코드' }]
RegionCode;
@UI.lineItem: [{ position: 20, label: '순위' }]
RankPosition;
@UI.lineItem: [{ position: 30, label: '총 매출' }]
TotalRevenue;
@UI.lineItem: [{ position: 40, label: '전년 성장률' }]
YoyGrowth;
}
권한 제어는 DCL로 추가합니다.
@EndUserText.label: 'Access rule for revenue ranking'
@MappingRole: true
define role ZC_REVENUE_RANKING {
grant select on ZC_RevenueRanking
where (RegionCode) = aspect pfcg_auth(
ZAUTH_REGION, REG_CODE, ACTVT = '03' );
}
6. 성능과 운영 고려사항
Table Function은 강력하지만 운영 환경에서 주의할 점이 있습니다. 첫째, HANA 전용이라는 제약입니다. 시스템이 HANA 외 DB로 이전할 계획이 있다면 대체 구현(Open SQL 기반 CDS + CDS Table Function 감싼 Wrapper)을 함께 설계하는 편이 안전합니다.
둘째, 트랜스포트 순서입니다. CDS Table Function DDL과 AMDP 클래스가 별도 오브젝트이므로 둘 다 같은 트랜스포트에 포함되어야 합니다. 권장 이송 순서는 다음과 같습니다.
- 테이블/데이터 엘리먼트 (DDIC)
- AMDP 클래스 (
CLAS) - CDS Table Function DDL (
DDLS) - 래퍼 CDS View Entity
- Metadata Extension, DCL
- Service Definition/Binding 재활성화
셋째, 권한입니다. HANA 측 _SYS_REPO 뷰 활성화 권한과 ABAP 측 S_DEVELOP 오브젝트 유형 DDLS/CLAS가 모두 필요합니다.
넷째, 디버깅입니다. AMDP는 ABAP 디버거로 스텝인 할 수 있지만 SQLScript 로직 내부는 External Break-point가 제한적입니다. HANA Studio 혹은 Database Explorer에서 생성된 함수를 SQL 콘솔로 직접 호출하며 파라미터를 바꿔가며 확인하는 방법이 실무에서 가장 효과적입니다.
7. 자주 발생하는 오류와 해결법
활성화 시 "Return type does not match implementing method" 오류
CDS DDL의 returns 필드 순서, 이름, 타입이 AMDP RETURN SELECT ...의 컬럼과 정확히 일치해야 합니다. 특히 client 필드는 CDS 쪽에 선언했다면 SELECT에도 반드시 포함되어야 하며, 앞자리에 배치하는 것이 권장됩니다.
HANA "feature not supported: session context" 메시지
SESSION_CONTEXT('CLIENT')를 사용하려면 HANA Revision이 충분해야 하고, @ClientHandling.algorithm: #SESSION_VARIABLE이 CDS Table Function에 선언되어 있어야 합니다. 실패하면 WHERE mandt = :p_client 파라미터를 추가해 우회합니다.
트랜스포트 후 QA 시스템에서 "function not found"
CDS Table Function DDL과 AMDP 클래스가 별도 트랜스포트에 나뉘어 이송된 경우입니다. 반드시 둘을 같은 Task에 묶어 이송하거나, AMDP 클래스 이송 완료 후 DDL을 이송해야 합니다.
BY DATABASE FUNCTION vs BY DATABASE PROCEDURE 혼동
일반 AMDP(BY DATABASE PROCEDURE)와 Table Function용 AMDP(BY DATABASE FUNCTION FOR HDB)는 선언 방식이 다릅니다. FOR TABLE FUNCTION으로 선언된 메서드는 반드시 BY DATABASE FUNCTION FOR HDB를 사용해야 하며, PROCEDURE로 구현하면 컴파일 오류가 납니다.
상위 CDS View에서 파라미터 전달 문법 오류
래퍼 CDS View에서 Table Function을 as select from ZTF_...(p_year => :p_year) 형태로 참조할 때 => 연산자와 파라미터명에 콜론(:)을 잊기 쉽습니다. 상위 뷰 파라미터는 :p_파라미터명으로, 함수 파라미터는 p_함수파라미터명 =>로 연결합니다.
8. 이 다음으로 살펴볼 주제들
CDS Table Function에 익숙해졌다면 다음 주제로 확장할 수 있습니다.
- CDS Hierarchy와 Table Function 결합 — 조직 계층 롤업 시나리오
- CDS View Entity(신규 문법)에서의 Table Function 참조 방식 차이
- Analytical CDS View(
@Analytics.dataCategory)로 승격시켜 KPI 대시보드 구성 - RAP(RESTful ABAP Programming Model)와 결합해 읽기 전용 비즈니스 오브젝트로 노출
- SAP Analytics Cloud와의 Live Connection으로 실시간 지역 매출 시각화
댓글 0
아직 댓글이 없습니다.