개요 및 이 글에서 다룰 것
ABAP Analytical Query는 SAP S/4HANA 및 BTP ABAP Environment에서 OLAP 스타일의 다차원 분석을 가능하게 하는 핵심 기술입니다. 그 중심에는 CDS Cube View(@Analytics.dataCategory: #CUBE)가 자리잡고 있으며, 이는 Fact 데이터와 Dimension을 결합해 Query View에서 슬라이스/다이스가 가능한 구조를 제공합니다. 이번 글에서는 Cube View의 설계 패턴, Association 기반 마스터데이터 조인, Restricted Measure를 활용한 KPI 계산 방법까지 단계별로 정리합니다.
- Analytical CDS 계층 구조(Dimension - Cube - Query) 이해
- 금액/통화 시맨틱 어노테이션 설계
- Restricted Measure로 조건부 집계 구현
- 성능 튜닝과 권한 통제 패턴 학습
사전 이해 항목
읽기 전 알아두면 좋은 항목은 다음과 같습니다. CDS View Entity 기본 문법, SELECT FROM ~ ASSOCIATION TO 구문, @Semantics 어노테이션 개념, 그리고 ADT(Eclipse) 사용 경험이 있으면 따라하기 수월합니다. S/4HANA의 Virtual Data Model(VDM) 계층(I_, C_, P_ Prefix) 명명 규칙도 알아두면 도움이 됩니다.
환경 및 준비물
아래 환경에서 검증된 패턴을 다룹니다. 버전에 따라 일부 어노테이션이 다를 수 있으므로 ADT 자동완성을 함께 확인하는 것이 권장됩니다.
- SAP S/4HANA 2022 FPS02 이상 또는 SAP BTP ABAP Environment 2305 이상
- ABAP Development Tools (ADT) for Eclipse 2024-03 릴리스 이상
- HANA DB 2.0 SPS07 (Analytical Engine 활성화)
- Fiori Launchpad 또는 Analysis for Office 2.8 이상 (Query 검증용)
- 권한:
S_RS_AUTH,S_DEVELOP(CDS 객체),S_RS_COMP(Query) - 샘플 데이터: EPM 또는 Bestseller 모델 권장
핵심 개념
Analytical Query는 3계층으로 구성됩니다. 가장 아래에 Dimension View(#DIMENSION)가 있고, 그 위에 트랜잭션 데이터를 모으는 Cube View(#CUBE), 최상단에 사용자에게 노출되는 Query View(#AGGREGATIONLEVEL)가 배치됩니다.
비유하자면 Dimension은 좌표축, Cube는 좌표 공간 안에 놓인 데이터 점들, Query는 사용자가 들고 있는 카메라입니다. 카메라(Query)는 축(Dimension)을 회전시켜 같은 점들(Cube)을 다양한 각도로 비춥니다.
- Dimension: 고객, 제품, 조직 등 마스터데이터.
@ObjectModel.dataCategory: #DIMENSION선언. - Cube: Fact 테이블 기반. Dimension을 Association으로 연결, Measure를 정의.
- Query: 사용자 노출용.
@Analytics.query: true를 선언하고 Selection/Restricted/Calculated Measure 등을 추가.
Cube에서 결정해야 할 핵심 사항은 (1) 어떤 필드가 Dimension Key로 외부 노출되는가, (2) 어떤 필드가 Measure인가, (3) 통화/단위 시맨틱을 어떻게 부여할 것인가입니다. @Semantics.amount.currencyCode는 금액 필드 옆 통화 필드를 연결해 단위 환산과 표시를 자동화합니다. @DefaultAggregation: #SUM은 집계 함수의 기본값을 결정합니다.
Restricted Measure는 동일 Cube 안에서 "특정 조건을 만족하는 행만 집계"하는 가상 Measure입니다. 예를 들어 "완료 상태인 주문의 매출만"을 별도 컬럼으로 만들 때 사용하며, 일반 SQL의 SUM(CASE WHEN ... THEN ... END)와 유사합니다.
실전 코드 3단계
1단계: 기본 Dimension과 Cube
먼저 제품 마스터를 Dimension으로 정의합니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Product Dimension'
@Analytics.dataCategory: #DIMENSION
@ObjectModel.representativeKey: 'Product'
define view entity ZI_ProductDim
as select from /dmo/product as p
association [0..1] to I_Currency as _Currency
on $projection.Currency = _Currency.Currency
{
key p.product_id as Product,
p.name as ProductName,
p.category as Category,
@Semantics.currencyCode: true
p.currency_code as Currency,
_Currency
}
이어서 매출 Fact를 기반으로 Cube를 작성합니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Cube'
@Analytics.dataCategory: #CUBE
@VDM.viewType: #COMPOSITE
define view entity ZI_SalesCube
as select from /dmo/booking as b
association [1..1] to ZI_ProductDim as _Product
on $projection.Product = _Product.Product
{
key b.booking_id as BookingId,
b.customer_id as CustomerId,
b.product_id as Product,
b.booking_date as BookingDate,
@Semantics.amount.currencyCode: 'Currency'
@DefaultAggregation: #SUM
b.flight_price as RevenueAmount,
@Semantics.currencyCode: true
b.currency_code as Currency,
_Product
}
이 단계에서 핵심은 Measure인 RevenueAmount에 @Semantics.amount.currencyCode를 붙여 통화 필드와 연결한 점입니다. 통화 어노테이션이 없으면 Query 계층에서 금액 합계가 비정상으로 표시될 수 있습니다.
2단계: 실무 Cube + Restricted Measure 적용 Query
이제 Cube 위에 Query View를 얹고, 완료 상태/취소 상태별 매출을 Restricted Measure로 분리합니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Analytical Query'
@Analytics.query: true
@OData.publish: true
define view entity ZC_SalesQuery
as select from ZI_SalesCube
{
@AnalyticsDetails.query.axis: #ROWS
@Consumption.filter.selectionType: #SINGLE
Product,
@AnalyticsDetails.query.axis: #ROWS
CustomerId,
@AnalyticsDetails.query.axis: #COLUMNS
BookingDate,
@Semantics.amount.currencyCode: 'Currency'
@DefaultAggregation: #SUM
RevenueAmount,
@Semantics.amount.currencyCode: 'Currency'
@DefaultAggregation: #FORMULA
@AnalyticsDetails.query.formula:
'NDIV0( ConfirmedRevenue / RevenueAmount ) * 100'
cast( 0 as abap.dec(15,2) ) as ConfirmationRate,
@Semantics.amount.currencyCode: 'Currency'
@DefaultAggregation: #SUM
@AnalyticsDetails.query.restrictedMeasure: [
{ selectionVariable: 'BookingStatus',
operation: #EQ,
low: 'B' }
]
RevenueAmount as ConfirmedRevenue,
Currency
}
여기서 ConfirmedRevenue는 동일한 RevenueAmount 컬럼을 사용하지만 BookingStatus = 'B'(Booked) 조건만 적용된 가상 Measure입니다. 이어서 ConfirmationRate는 @AnalyticsDetails.query.formula로 정의된 Calculated Measure로, 0 나눔을 방지하는 NDIV0 함수를 사용합니다. @AnalyticsDetails.query.axis로 행/열 배치 기본값을 잡아두면 분석 도구에서 즉시 사용이 편리합니다.
3단계: 프로덕션 - 권한, 성능, 검증
실제 운영 환경에서는 DCL 기반 권한, 입력 파라미터, 성능 힌트를 함께 설계합니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Cube with Auth'
@Analytics.dataCategory: #CUBE
@ClientHandling.algorithm: #SESSION_VARIABLE
define view entity ZI_SalesCubeProd
with parameters
p_FromDate : abap.dats,
p_ToDate : abap.dats
as select from /dmo/booking as b
association [1..1] to ZI_ProductDim as _Product
on $projection.Product = _Product.Product
{
key b.booking_id as BookingId,
b.customer_id as CustomerId,
b.product_id as Product,
b.booking_date as BookingDate,
@Semantics.amount.currencyCode: 'Currency'
@DefaultAggregation: #SUM
b.flight_price as RevenueAmount,
@Semantics.currencyCode: true
b.currency_code as Currency,
_Product
}
where b.booking_date between $parameters.p_FromDate
and $parameters.p_ToDate;
@EndUserText.label: 'Auth for Sales Cube'
@MappingRole: true
define role ZI_SalesCube_Auth {
grant select on ZI_SalesCubeProd
where (CustomerId) =
aspect pfcg_auth( ZSALES_AUTH, CUSTID, ACTVT = '03' );
}
운영 단계 체크리스트는 다음과 같습니다.
- DCL:
define role로 행 수준 보안을 적용하고 PFCG 권한 객체와 매핑. - 파라미터: 큰 Fact 테이블은 날짜/조직 등 필수 입력으로 받아 HANA 옵티마이저가 파티션을 잘라낼 수 있도록 함.
- 성능:
@Analytics.internalName,@VDM.viewType: #COMPOSITE일관성, 가능하면 Calculated Field를 Query 레이어로 미루기. - 테스트: ADT의 Data Preview, RSRT 트랜잭션(또는 BTP의 Analytics Cockpit), Fiori "Query Browser" 앱으로 검증.
- 릴리스:
@EndUserText, API 상태(@API.state) 및 ATC 검사 통과 후 Transport.
흔한 실수와 트러블슈팅
경험상 가장 자주 만나는 함정을 FAQ 형태로 정리합니다.
- Q1. Activation 시 "Element has no aggregation" 오류가 발생합니다.
Cube에서는 모든 비키 필드가 Dimension 또는 Measure 중 하나로 명확히 분류되어야 합니다. Measure에는@DefaultAggregation(보통#SUM), Dimension에는@DefaultAggregation: #NONE또는 키 지정을 추가하세요. - Q2. Restricted Measure가 빈 값으로만 나옵니다.
제약 조건에 사용한 필드가 Cube의 출력 컬럼에 포함되어 있어야 합니다. 또한selectionVariable로 참조한 변수가 Query에 선언되어 있는지 확인하세요. Booked/Cancelled 상태처럼 Code 값을 비교할 때는 도메인 값(대소문자 포함)을 정확히 맞춰야 합니다. - Q3. Query에서 통화 합계가 "*"로 표시됩니다.
이는 여러 통화가 섞여 단순 합계가 의미 없을 때 나타나는 일반적 동작입니다. 통화 변환을 위해@Semantics.amount.currencyCode가 올바르게 지정되었는지, 그리고 필요시CURRENCY_CONVERSION을 사용해 단일 통화로 변환하는지 점검합니다. - Q4. Cube를 Query 없이 OData로 노출하면 안 되나요?
가능은 하지만 권장되지 않습니다. Cube는 내부 모델, Query는 소비 계층이라는 분리가 향후 변경 비용을 줄입니다.
또한 ATC가 "Naming convention" 경고를 띄우는 경우가 많은데, S/4HANA VDM 규칙(I_ Interface, C_ Consumption, P_ Private)을 따르는 것이 유지보수에 유리합니다.
다음 단계와 관련 주제
여기까지 익혔다면 다음 주제로 확장해 보세요. 첫째, Hierarchy 어노테이션(@Hierarchy.parentChild)으로 조직/제품 카테고리 드릴다운 구현. 둘째, Input Parameters와 Variables를 활용한 동적 통화 변환 및 회계 기간 필터. 셋째, Embedded Analytics로 SAP Analytics Cloud(SAC) Live 모델과 연결. 마지막으로 BTP ABAP Environment에서는 RAP + Analytical Query 조합으로 OData V4 Analytics를 노출하고 Fiori Elements Analytical List Page와 결합하는 패턴도 함께 살펴보면 좋습니다.
참고 자료
- SAP Help - CDS Analytical Model 개요
- SAP Help - @Analytics.dataCategory 어노테이션
- SAP Help - @Semantics 어노테이션 레퍼런스
- SAP Help - S/4HANA Embedded Analytics
- SAP Developers - Building an Analytical Query Tutorial
- SAP Community - CDS Views 태그 모음
핵심 한 줄
Dimension - Cube - Query 3계층 분리와 시맨틱 어노테이션, Restricted Measure 패턴을 지키는 것이 견고한 ABAP Analytical Query 설계의 출발점입니다.
댓글 0
아직 댓글이 없습니다.