ABAP

Analytical Query, Cube View 없이 짜면 큰일 #shorts #SAP #ABAP

개요 및 이 글에서 다룰 것

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와 결합하는 패턴도 함께 살펴보면 좋습니다.

참고 자료

핵심 한 줄

Dimension - Cube - Query 3계층 분리와 시맨틱 어노테이션, Restricted Measure 패턴을 지키는 것이 견고한 ABAP Analytical Query 설계의 출발점입니다.

댓글 0

아직 댓글이 없습니다.