ABAP

Analytical Query DrillDown 설정 전 axis 금지 패턴 3개 #shorts #SAP #ABAP

개요 및 도달 지점

ABAP Analytical Query는 SAP BTP ABAP Environment 및 S/4HANA 2022 이상에서 제공되는 강력한 분석 모델링 도구입니다. 이 글에서는 Analytical Query CDS View에 DrillDown(드릴다운) 축을 정의하고, 계층(Hierarchy) CDS View를 연결하여 Fiori Elements Analytical List Page(ALP)에서 다차원 분석을 구현하는 방법을 단계적으로 다룹니다.

  • Analytical Query의 ROW/COLUMN/FREE 축 의미 이해
  • @AnalyticsDetails.query 어노테이션으로 DrillDown 구성
  • Parent-Child 계층 CDS View 정의 및 연결
  • ALP에서 차트/테이블 동시 시각화
  • 정렬, 필터, 변수(Variable) 설정 베스트 프랙티스

알아두면 좋은 배경

이 글을 따라가려면 CDS View(@AbapCatalog, @Analytics.dataCategory) 작성 경험이 있으면 좋습니다. 또한 ABAP Development Tools(ADT)에서 CDS View 생성, Cube/Dimension View 차이, Fiori Elements 플로어플랜의 기본 개념(List Report vs ALP)을 알면 이해가 빠릅니다. RAP(ABAP RESTful Application Programming Model)의 Behavior 정의는 분석 시나리오에는 필수는 아니지만, Read-only 서비스 노출 방식은 일반적으로 알고 있어야 합니다.

환경 / 버전 / 준비물

아래 환경에서 검증된 예제를 다룹니다. 버전이 다를 경우 어노테이션 일부가 deprecated 처리될 수 있으니 SAP Note에서 확인을 권장합니다.

  • SAP BTP ABAP Environment (Steampunk) 2024 Release 또는 S/4HANA 2022 FPS02 이상
  • ABAP Development Tools (ADT) 3.40 이상
  • SAP Gateway / OData V4 활성화
  • SAP Fiori Elements ALP 템플릿 (SAP UI5 1.120 이상 권장)
  • 분석 권한(S_RS_AUTH) 또는 BTP 환경의 Analytics Catalog Role
  • 샘플 데이터: 판매오더(Sales Order) 테이블 또는 EPM 모델(SNWD_SO_INV_HEAD 유사 구조)

ALP를 제대로 보려면 백엔드의 OData $apply (Aggregation) 지원이 필요합니다. BTP ABAP Environment에서는 Service Binding 시 OData V4 UI 옵션을 선택해야 분석용 메타데이터가 자동 생성됩니다.

핵심 개념

Analytical Query는 다차원 큐브(Cube) 위에 정의되는 보고서 레이어입니다. 일반 CDS View가 평면적인 결과를 반환한다면, Analytical Query는 OLAP 엔진을 통해 집계(aggregation), 드릴다운(drill-down), 슬라이스(slice)를 수행합니다.

비유하자면, Cube View는 데이터의 "창고"이고 Query View는 그 창고 안에서 어떤 박스를 어떤 선반에 놓고 볼지 결정하는 "진열 설계도"라고 할 수 있습니다.

세 가지 축(Axis)

  • ROW: 행으로 표시되는 디멘션. 일반적으로 조직, 시간, 제품 등 계층 구조를 가진 항목.
  • COLUMN: 열로 표시되는 디멘션 또는 측정값(KPI). 매출, 수량 등 Measure가 여기 위치.
  • FREE: 초기에는 보이지 않지만 사용자가 필요 시 드릴다운/드래그 가능한 항목. "숨겨진 옵션"으로 생각하면 됩니다.

계층(Hierarchy)

계층은 Parent-Child 관계를 가진 별도 CDS View로 모델링합니다. 예를 들어 "제품 카테고리 → 제품 그룹 → 제품"처럼 트리를 형성합니다. @Hierarchy.parentChild 어노테이션으로 부모-자식 관계를 명시하고, Query View의 디멘션에 @Consumption.valueHelpDefinition 또는 @ObjectModel.hierarchy.association으로 연결합니다.

도식

[Interface View ZI_*] -> [Cube View ZC_*] -> [Query View ZQ_*]
                                  |
                                  +--> [Hierarchy View ZH_*]
                                  |
                                  +--> [Service Definition ZSD_*] -> [Service Binding (OData V4 UI)]
                                                                       |
                                                                       v
                                                              [Fiori Elements ALP]

실전 코드 3단계

1단계: 기본 Analytical Query 정의

먼저 Cube View가 있다고 가정하고, 그 위에 가장 단순한 Query View를 작성합니다. 매출액을 제품별로 보여주는 보고서입니다.

@AbapCatalog.sqlViewName: 'ZQSALESBASIC'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Query - Basic Drilldown'
@Analytics.query: true
@OData.publish: false
define view ZQ_SALES_BASIC
  as select from ZC_SALES_CUBE
{
  @AnalyticsDetails.query.axis: #ROWS
  @AnalyticsDetails.query.displayHierarchy: #LEVEL
  ProductID,

  @AnalyticsDetails.query.axis: #ROWS
  ProductCategory,

  @AnalyticsDetails.query.axis: #COLUMNS
  @AnalyticsDetails.query.sortOrder: #DESCENDING
  @DefaultAggregation: #SUM
  NetAmount,

  @AnalyticsDetails.query.axis: #COLUMNS
  @DefaultAggregation: #SUM
  Quantity,

  @AnalyticsDetails.query.axis: #FREE
  CustomerID,

  @AnalyticsDetails.query.axis: #FREE
  SalesOrganization,

  CurrencyCode
}

여기서 ProductID와 ProductCategory는 ROWS에 배치되어 기본 드릴다운 경로가 됩니다. NetAmount, Quantity는 COLUMNS에 위치한 측정값이며, CustomerID와 SalesOrganization은 FREE축에 두어 사용자가 원할 때만 펼치도록 했습니다.

2단계: 계층 연결과 정렬, 변수 추가

실무에서는 단순 평면 디멘션보다 "조직 계층", "제품 카테고리 계층"이 자주 사용됩니다. 계층 CDS View를 만들고 Query에서 참조합니다.

@AbapCatalog.sqlViewName: 'ZHPRODCAT'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Product Category Hierarchy'
@Hierarchy.parentChild: [{
  name: 'PRODCAT_H',
  recurse: { parent: 'ParentCategory', child: 'CategoryID' },
  siblingsOrder: [{ by: 'CategoryID', direction: #ASC }],
  multipleParents: #NOT_ALLOWED,
  orphans: #ROOT
}]
define view entity ZH_PROD_CATEGORY
  as select from zproduct_cat as cat
{
  key cat.category_id    as CategoryID,
      cat.parent_cat_id  as ParentCategory,
      cat.category_name  as CategoryName
}

이제 Query View에서 ProductCategory에 위 계층을 연결하고, 변수(Variable)와 사용자 입력 필터를 추가합니다.

@AbapCatalog.sqlViewName: 'ZQSALESHIER'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Query with Hierarchy'
@Analytics.query: true
@VDM.viewType: #CONSUMPTION
define view ZQ_SALES_HIER
  with parameters
    P_DisplayCurrency : vdm_v_display_currency,
    P_ExchangeRateType: kurst_curr,
    P_KeyDate         : vdm_v_key_date
  as select from ZC_SALES_CUBE
{
  @AnalyticsDetails.query.axis: #ROWS
  @ObjectModel.hierarchy.association: '_ProductCategoryHier'
  @Consumption.filter: { selectionType: #HIERARCHY_NODE, multipleSelections: true }
  ProductCategory,

  @AnalyticsDetails.query.axis: #ROWS
  @AnalyticsDetails.query.sortOrder: #ASCENDING
  ProductID,

  @AnalyticsDetails.query.axis: #COLUMNS
  @AnalyticsDetails.query.sortOrder: #DESCENDING
  @AnalyticsDetails.query.totals: #SHOW
  @Semantics.amount.currencyCode: 'DisplayCurrency'
  @DefaultAggregation: #SUM
  NetAmount,

  @AnalyticsDetails.query.axis: #FREE
  @AnalyticsDetails.query.hierarchyInitialLevel: 2
  SalesOrganization,

  // currency conversion
  @Semantics.currencyCode: true
  $parameters.P_DisplayCurrency as DisplayCurrency,

  /* association to hierarchy */
  _ProductCategoryHier
}
association [1..1] to ZH_PROD_CATEGORY as _ProductCategoryHier
  on $projection.ProductCategory = _ProductCategoryHier.CategoryID

핵심 포인트는 다음과 같습니다.

  • @ObjectModel.hierarchy.association으로 계층 어소시에이션을 명시
  • @AnalyticsDetails.query.totals: #SHOW로 합계 행 표시
  • @Consumption.filter.selectionType: #HIERARCHY_NODE로 계층 노드 단위 필터 가능
  • 파라미터를 통해 표시 통화/환산일자를 사용자 입력으로 받음

3단계: 프로덕션 - 권한, 성능, ALP 연동

실제 운영 환경에서는 DCL(Data Control Language)로 행 단위 권한을 적용하고, Service Definition을 통해 ALP에 노출합니다.

@EndUserText.label: 'DCL for Sales Query'
@MappingRole: true
define role ZDCL_SALES_QUERY {
  grant select on ZQ_SALES_HIER
    where SalesOrganization =
      aspect pfcg_auth ( S_SALES_ORG, VKORG, ACTVT = '03' );
}
@EndUserText.label: 'Service Def for Sales Analytics'
define service ZSD_SALES_ANALYTICS {
  expose ZQ_SALES_HIER as SalesQuery;
  expose ZH_PROD_CATEGORY as ProductHierarchy;
}

Service Binding(OData V4 - UI)을 생성하면 메타데이터에 Aggregation.ApplySupported가 노출됩니다. ALP는 이 정보를 읽어 차트와 테이블을 동시에 렌더링합니다. UI 어노테이션은 별도 메타데이터 확장으로 분리하는 것이 일반적으로 권장됩니다.

@Metadata.layer: #CORE
annotate entity ZQ_SALES_HIER with
{
  @UI.chart: [{
    qualifier: 'SalesByCategory',
    chartType: #COLUMN,
    dimensions: ['ProductCategory'],
    measures: ['NetAmount'],
    dimensionAttributes: [{ dimension: 'ProductCategory', role: #CATEGORY }],
    measureAttributes: [{ measure: 'NetAmount', role: #AXIS_1, asDataPoint: true }]
  }]
  @UI.presentationVariant: [{
    qualifier: 'Default',
    sortOrder: [{ by: 'NetAmount', direction: #DESC }],
    visualizations: [
      { type: #AS_CHART, qualifier: 'SalesByCategory' },
      { type: #AS_LINEITEM }
    ]
  }]
  @UI.selectionVariant: [{
    qualifier: 'CurrentYear',
    parameters: [{ name: 'P_KeyDate', value: '20260101' }]
  }]
  ProductCategory;

  @UI.lineItem: [{ position: 20, label: 'Net Amount' }]
  @UI.dataPoint: { title: 'Net Amount', targetValue: 1000000 }
  NetAmount;
}

성능 측면 팁:

  • Cube View 단계에서 @Analytics.dataExtraction.enabled: true 설정 시 추출 시나리오 고려
  • 큰 디멘션은 FREE 축에 두어 초기 쿼리 비용 감소
  • HDB 통계 활성화 및 적절한 인덱스 확인
  • 단위 테스트는 CL_CDS_TEST_ENVIRONMENT 또는 RAP의 TDF로 구성 가능

흔한 실수 / 트러블슈팅

Analytical Query 구현 시 자주 만나는 문제와 해결책입니다.

FAQ 1. ALP에서 차트가 비어 보입니다.

대부분 OData 메타데이터에 Aggregation 어노테이션이 누락된 경우입니다. Service Binding을 OData V4 "UI" 타입으로 만들었는지, Cube/Query View에 @Analytics.dataCategory: #CUBE, @Analytics.query: true가 올바른지 확인합니다. 또한 측정값에 @DefaultAggregation이 빠지면 ALP는 집계 불가능으로 판단합니다.

FAQ 2. 계층 드릴다운이 동작하지 않습니다.

Hierarchy View의 @Hierarchy.parentChild 정의에서 recurse.parentrecurse.child 필드가 같은 도메인/타입이어야 합니다. 또한 Query View에서 @ObjectModel.hierarchy.association은 정의된 어소시에이션 이름과 정확히 일치해야 하며, 루트 노드(orphans 처리)가 비어 있으면 트리가 그려지지 않습니다.

FAQ 3. 정렬 어노테이션이 무시됩니다.

@AnalyticsDetails.query.sortOrder는 디멘션 정렬과 측정값 정렬에 적용 방식이 다릅니다. 측정값에 적용한 경우 ALP의 PresentationVariant.sortOrder가 우선합니다. UI 어노테이션 레벨에서 한 번 더 명시하는 것을 일반적으로 권장합니다.

FAQ 4. 권한 오류(NOT AUTHORIZED)가 발생합니다.

DCL은 Cube View와 Query View 양쪽에 정의되어야 합니다. Query View에만 두면 하위 Cube에서 필터링되지 않아 누수가 발생하거나, 반대로 권한 충돌로 빈 결과가 나올 수 있습니다.

다음 단계 / 관련 주제

Analytical Query를 익혔다면 다음 주제로 확장해 보세요.

  • Calculated Measure와 Restricted Measure를 활용한 KPI 모델링
  • Analytical Engine의 Exception Aggregation(예: COUNT DISTINCT) 동작
  • SAP Analytics Cloud(SAC)에서 Live Connection으로 Query View 소비
  • RAP Read-only 시나리오와 Draft 없는 분석 서비스 결합
  • Embedded Analytics vs Side-by-side Analytics 비교

참고 자료

핵심 한 줄

Analytical Query는 ROW/COLUMN/FREE 축 설계와 Parent-Child 계층 연결, 그리고 ALP의 PresentationVariant 조합으로 완성된다.

댓글 0

아직 댓글이 없습니다.