News

DefaultAggregation SUM vs MAX vs MIN vs COUNT vs NONE — CUBE View 집계 완전 정복

▶ YouTube에서 보기

이 글이 답하는 질문

  • DefaultAggregation 어노테이션은 왜 필요한가?
  • SUM, MAX, MIN, COUNT, NONE 각각 언제 쓰는가?
  • 통화 코드나 Dimension 필드에 SUM을 붙이면 어떤 오류가 생기는가?
  • Query Browser에서 집계 오류를 어떻게 확인하는가?

이 글은 SAP S/4HANA 2023 ABAP for Cloud Development 환경에서 Analytical Query를 설계할 때, CUBE View의 측정값(Measure)에 어떤 집계 방식을 선언해야 하는지를 다룹니다. 글을 읽고 나면 SUMNONE을 구분해서 쓸 수 있고, 잘못된 집계 선언이 Query Browser에서 어떻게 드러나는지 진단할 수 있게 됩니다.

체크리스트로 정리하면 다음과 같습니다.

  • 측정 가능한 숫자 필드와 단순 분류 코드를 구별할 수 있다
  • 금액·수량 컬럼에 @DefaultAggregation: #SUM을 정확히 부여할 수 있다
  • 통화 코드, 단위, 상태 코드 같은 Dimension 후보 필드에 #NONE을 선언하는 이유를 설명할 수 있다
  • Query Browser 실행 시 집계 오류가 어떤 형태로 나타나는지 알아챌 수 있다

이 글을 보기 전에

다음 세 가지 개념은 이 글의 모든 코드를 이해하는 데 기본 전제가 됩니다.

  • ABAP CDS View 기본 문법define view entity 키워드, select from 절, association 정의, 어노테이션 표기 규칙
  • @Analytics.dataCategory #CUBE 개념 — Cube View가 Fact 데이터를 담는 분석 모델이며, Dimension View와 association으로 연결된다는 점
  • Analytical Query 3층 구조 — Interface View(I_*)에서 데이터 정제, Cube View(C_*)에서 집계 선언, Query View(C_*Query)에서 화면 표현을 분리하는 구조

위 세 가지가 익숙하지 않다면 AQ 기초 1편(CDS Interface), 2편(@Analytics.dataCategory) 시리즈를 먼저 읽고 오는 것을 권장합니다.

환경 / 버전 / 준비물

이 글의 모든 코드는 다음 환경에서 검증되었습니다.

  • SAP S/4HANA 2023, On-Premise 또는 Private Cloud Edition
  • ABAP for Cloud Development (Steampunk 호환 모드)
  • ABAP Development Tools (ADT) for Eclipse 2024-03 이상
  • SAP Fiori Launchpad의 Query Browser (앱 ID: F1538)
  • 샘플 테이블: I_SalesOrderItem (표준 릴리즈 CDS), 또는 자체 ZTAB 사용 가능

실습을 따라가려면 ADT에서 $TMP 또는 사용자 패키지에 CDS View를 생성할 권한이 필요하며, Query Browser 접근 권한(S_RS_AUTH 또는 카탈로그 SAP_BR_ANALYTICS_SPECIALIST)이 있어야 결과를 확인할 수 있습니다.

DefaultAggregation이란 무엇인가 — CUBE View의 집계 선언

@DefaultAggregation은 CDS View의 각 필드가 "측정값(Measure)인가, 분류값(Dimension)인가"를 선언하는 어노테이션입니다. Analytical Engine이 Query를 실행할 때, 이 어노테이션을 기준으로 GROUP BY를 자동 생성하기 때문에 잘못 붙이면 결과가 통째로 어긋납니다.

비유하자면 엑셀 피벗 테이블을 생각하면 쉽습니다. 피벗을 만들 때 "값" 영역에 끌어다 놓을 수 있는 필드는 숫자 합계, 평균, 최대 같은 수치 집계가 가능한 컬럼이고, "행/열" 영역에 끌어다 놓는 필드는 그 자체로 카테고리가 되는 코드·이름입니다. @DefaultAggregation은 엑셀이 자동으로 판정하던 그 구분을 CDS 모델에 명시적으로 적어두는 역할을 합니다.

핵심 값은 다섯 가지입니다.

  • #SUM — 합산. 금액, 수량 등 누적이 의미 있는 수치
  • #MAX — 최댓값. 그룹 내 가장 큰 값을 가져옴
  • #MIN — 최솟값. 그룹 내 가장 작은 값을 가져옴
  • #COUNT — 건수 집계. 그룹 내 행 개수를 셈
  • #NONE — 집계하지 않음. Dimension 후보 또는 단위·통화 코드처럼 합산이 무의미한 필드

도식으로 표현하면 다음과 같습니다.

+--------------------------------------------+
| CUBE View Field                            |
+--------------------------------------------+
|  Numeric Measure  ----> #SUM / #MAX / #MIN |
|  Row Count Field  ----> #COUNT             |
|  Currency / UoM   ----> #NONE              |
|  Key Code (Dim)   ----> #NONE 또는 미선언  |
+--------------------------------------------+

여기서 가장 자주 헷갈리는 부분이 통화 코드(CurrencyCode)와 단위 코드(UnitOfMeasure)입니다. 두 필드는 숫자처럼 보이지 않지만 금액·수량과 항상 함께 다녀야 하기 때문에 Measure 가까이에 선언됩니다. 그러나 절대 #SUM을 붙여서는 안 되며, #NONE으로 명시하거나 아무것도 붙이지 않아 Dimension으로 남겨야 합니다.

SUM — 금액·수량 합산: 가장 많이 쓰는 집계 방식

가장 흔한 케이스는 매출 금액, 주문 수량, 재고량 같은 컬럼입니다. 이런 필드는 Query Browser에서 사용자가 "월별 매출 합계", "지역별 주문량 합계"를 보고 싶을 때마다 자동으로 합산되어야 합니다. #SUM은 Analytical Engine에게 "이 필드는 합쳐도 의미가 유지된다"라고 알려주는 신호입니다.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Cube - SUM example'
@Analytics.dataCategory: #CUBE

define view entity ZC_SALES_CUBE_STEP1
  as select from I_SalesOrderItem
{
  key SalesOrder,
  key SalesOrderItem,

      Product,
      SoldToParty,

      @Semantics.amount.currencyCode: 'TransactionCurrency'
      @DefaultAggregation: #SUM
      NetAmount,

      @Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
      @DefaultAggregation: #SUM
      RequestedQuantity,

      @Semantics.currencyCode: true
      @DefaultAggregation: #NONE
      TransactionCurrency,

      @Semantics.unitOfMeasure: true
      @DefaultAggregation: #NONE
      OrderQuantityUnit
}

위 예시에서 NetAmountRequestedQuantity 두 필드만 합산 대상이며, 통화·단위 코드는 #NONE으로 명시했습니다. Query를 실행하면 결과적으로 SELECT SUM(NetAmount), SUM(RequestedQuantity) ... GROUP BY Product, SoldToParty, TransactionCurrency, OrderQuantityUnit 형태로 생성됩니다.

MAX·MIN — 최고가·최저가: 극값 집계

합산이 의미 없지만 그룹 내 극단값이 의미 있는 경우가 있습니다. 예를 들어 "고객별 최고 단가", "지점별 최저 재고일자" 같은 시나리오입니다. 이때는 #MAX 또는 #MIN을 사용합니다.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Cube - MAX/MIN example'
@Analytics.dataCategory: #CUBE

define view entity ZC_SALES_CUBE_STEP2
  as select from I_SalesOrderItem
{
  key SalesOrder,
  key SalesOrderItem,

      Product,
      SoldToParty,

      @Semantics.amount.currencyCode: 'TransactionCurrency'
      @DefaultAggregation: #SUM
      NetAmount,

      // 단가는 합산하면 의미가 깨짐 — 최댓값 집계
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      @DefaultAggregation: #MAX
      NetPriceAmount as MaxUnitPrice,

      // 같은 데이터를 최솟값으로 한 번 더 노출
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      @DefaultAggregation: #MIN
      NetPriceAmount as MinUnitPrice,

      @Semantics.currencyCode: true
      @DefaultAggregation: #NONE
      TransactionCurrency
}

여기서 핵심은 같은 원본 컬럼(NetPriceAmount)을 별칭(alias)으로 두 번 노출해 각각 다른 집계를 부여한 점입니다. Query Browser에서는 MaxUnitPriceMinUnitPrice가 별개 측정값으로 나타나며, 사용자가 "Product별 최대 단가와 최소 단가"를 한 화면에서 비교할 수 있습니다. 단가 자체에 #SUM을 붙이지 않은 점에 유의하세요. 단가는 합계가 비즈니스적으로 의미 없는 대표적 필드입니다.

COUNT·NONE — 건수 vs 집계 안 함

#COUNT는 "이 필드가 NULL이 아닌 행의 개수"를 세는 집계입니다. 주문 건수, 고객 수, 활성 계약 수처럼 "몇 개인가"를 알고 싶을 때 사용합니다.

// SalesOrder 건수를 세는 Measure
@DefaultAggregation: #COUNT
SalesOrder as OrderCount,

// 동일 필드를 Key로도 노출하려면 별도 별칭으로
key SalesOrder

반면 #NONE은 "이 필드는 집계하지 말고 Dimension처럼 GROUP BY 절에 포함시켜라"는 의미입니다. 통화 코드, 단위, 상태 코드, 회사 코드, 플랜트 코드 등이 여기에 해당합니다.

실무 팁: Key 필드에는 @DefaultAggregation을 붙이지 않는 것이 일반적입니다. Key는 정의상 그룹화 기준이 되기 때문입니다. #NONE은 Key가 아니지만 합산 의미가 없는 코드 필드에 명시적으로 붙입니다.

흔한 실수: 통화 코드에 SUM을 붙이면 생기는 Query Browser 오류

가장 자주 발생하는 실수는 다음 세 가지입니다.

  • 실수 1TransactionCurrency 같은 통화 코드 필드에 #SUM을 붙임. 활성화는 통과되지만 Query Browser 실행 시 "Aggregation behavior is inconsistent" 류의 메시지가 나오거나, 통화 코드가 문자처럼 합쳐져 (예: "USDUSDEUR") 결과가 깨짐
  • 실수 2 — 단가 필드에 #SUM을 붙임. 활성화도 통과되고 실행도 되지만, 의미적으로 단가의 합은 무의미하므로 비즈니스 사용자가 잘못된 KPI를 받게 됨
  • 실수 3 — 모든 숫자 필드에 동일하게 #SUM을 일괄 적용. 비율 필드(%), 평균 점수 같은 비가산(non-additive) 측정값까지 합산되어 KPI가 부풀려짐

FAQ

Q1. #SUM을 통화 필드에 붙였는데 활성화가 성공했습니다. 진짜 문제가 있는 건가요?

활성화는 구문 검증 단계라 통과될 수 있습니다. 그러나 Query Browser에서 해당 Query를 실행해 통화 코드를 행에 끌어다 놓는 순간 Analytical Engine이 통화 코드 컬럼을 측정값으로 해석해 GROUP BY에서 제외하고, 결과적으로 환율이 다른 금액이 한 셀에 섞여 표시됩니다. @Semantics.amount.currencyCode 참조 무결성 체크도 깨질 수 있습니다.

Q2. Key 필드에는 어떤 어노테이션을 붙여야 하나요?

Key는 본질적으로 Dimension이므로 @DefaultAggregation을 생략하는 것이 일반적인 패턴입니다. 명시적으로 표현하고 싶다면 #NONE을 붙여도 무방하지만, 가독성 면에서 생략을 권장합니다.

Q3. Query Browser에서 집계 오류를 어떻게 진단하나요?

Fiori Launchpad의 Query Browser(앱 ID F1538)에서 해당 Query View를 검색해 미리보기를 실행합니다. 측정값 영역에 있어야 할 필드가 행 영역에 나타나거나, 행 영역에 있어야 할 코드가 합산된 듯한 큰 숫자로 표시되면 @DefaultAggregation 선언을 의심합니다. ADT의 Data Preview(F8)에서는 단순 SELECT만 확인되므로 집계 오류는 드러나지 않습니다.

완성 코드 예시: SalesCube CUBE View

지금까지의 규칙을 종합한 프로덕션 수준의 SalesCube 예시입니다. Interface View가 이미 존재한다고 가정하며, 이 Cube 위에 별도 Query View를 올려 Fiori에 노출하는 구조입니다.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales Cube with full aggregation set'
@Metadata.allowExtensions: true
@Analytics.dataCategory: #CUBE
@Analytics.internalName: #LOCAL
@VDM.viewType: #COMPOSITE
@ObjectModel.usageType.serviceQuality: #X
@ObjectModel.usageType.sizeCategory: #L
@ObjectModel.usageType.dataClass: #MIXED

define view entity ZC_SALES_CUBE
  as select from I_SalesOrderItem as Item

  association [0..1] to I_Product       as _Product
    on $projection.Product = _Product.Product
  association [0..1] to I_Customer      as _Customer
    on $projection.SoldToParty = _Customer.Customer
  association [0..1] to I_CalendarDate  as _CreationDate
    on $projection.CreationDate = _CreationDate.CalendarDate

{
  // ---------- Keys (Dimension) ----------
  key Item.SalesOrder,
  key Item.SalesOrderItem,

  // ---------- Dimension 후보 ----------
  @ObjectModel.foreignKey.association: '_Product'
  Item.Product,

  @ObjectModel.foreignKey.association: '_Customer'
  Item.SoldToParty,

  @ObjectModel.foreignKey.association: '_CreationDate'
  Item.CreationDate,

  // ---------- Measures: 합산 ----------
  @Semantics.amount.currencyCode: 'TransactionCurrency'
  @DefaultAggregation: #SUM
  @EndUserText.label: 'Net Amount (Sum)'
  Item.NetAmount,

  @Semantics.quantity.unitOfMeasure: 'OrderQuantityUnit'
  @DefaultAggregation: #SUM
  @EndUserText.label: 'Order Quantity (Sum)'
  Item.RequestedQuantity,

  // ---------- Measures: 극값 ----------
  @Semantics.amount.currencyCode: 'TransactionCurrency'
  @DefaultAggregation: #MAX
  @EndUserText.label: 'Max Unit Price'
  Item.NetPriceAmount as MaxUnitPrice,

  @Semantics.amount.currencyCode: 'TransactionCurrency'
  @DefaultAggregation: #MIN
  @EndUserText.label: 'Min Unit Price'
  Item.NetPriceAmount as MinUnitPrice,

  // ---------- Measures: 건수 ----------
  @DefaultAggregation: #COUNT
  @EndUserText.label: 'Order Item Count'
  Item.SalesOrderItem as ItemCount,

  // ---------- Currency / UoM (집계 안 함) ----------
  @Semantics.currencyCode: true
  @DefaultAggregation: #NONE
  Item.TransactionCurrency,

  @Semantics.unitOfMeasure: true
  @DefaultAggregation: #NONE
  Item.OrderQuantityUnit,

  // ---------- Associations ----------
  _Product,
  _Customer,
  _CreationDate
}

이 Cube를 활성화한 뒤, 위에 @Analytics.query: true를 가진 Query View를 하나 더 만들어 Fiori Query Browser에 노출하면 다음과 같은 분석 시나리오가 가능해집니다.

  • 고객별 / 제품별 NetAmount 합계 비교
  • 제품별 단가 분포(최대·최소)와 평균 가격 추론
  • 월별 주문 라인 건수(ItemCount) 추세 분석
  • 통화별로 분리된 매출 합계 (TransactionCurrency가 자동으로 GROUP BY에 포함)

다음 단계 / 관련 주제

이 글에서 다룬 @DefaultAggregation은 Cube View의 골격을 잡는 어노테이션이지만, 실제 화면 표현은 Query View 단계에서 결정됩니다. 다음 주제로 이어가면 좋습니다.

  • AQ 기초 4편 — @AnalyticsDetails.query.axis로 Row/Column 기본 배치 지정
  • AQ 기초 5편 — @AnalyticsDetails.query.formula로 측정값 간 계산식 추가 (예: 평균 단가 = 합계금액 / 합계수량)
  • 심화 — Non-cumulative Measure, Exception Aggregation(@DefaultAggregation: #FORMULA) 활용
  • 운영 — Query Browser와 SAC(SAP Analytics Cloud) Live Connection 연동 시 집계 동작 차이

참고 자료

이 글의 모든 코드는 SAP S/4HANA 2023 환경 기준이며, 다른 릴리즈에서는 어노테이션 지원 범위가 다를 수 있으니 실제 시스템에서 ADT의 코드 어시스트로 사용 가능한 값을 다시 확인하는 것을 권장합니다.

댓글 0

아직 댓글이 없습니다.