1. RAP에서 집계 데이터가 필요한 이유
RAP(ABAP RESTful Application Programming Model)로 만든 트랜잭션 앱은 개별 레코드의 생성·수정에는 강하지만, 실무에서는 "이번 달 영업조직별 매출 합계", "고객별 주문 건수" 같은 집계 관점의 화면이 반드시 함께 요구됩니다. 이런 수치를 프론트엔드(Fiori Elements)나 외부 소비자가 매번 전체 데이터를 내려받아 계산하면 네트워크와 메모리를 낭비하게 됩니다. 집계는 데이터가 있는 곳, 즉 HANA 데이터베이스에서 수행하는 것이 일반적으로 권장되는 방식(Code Pushdown)입니다.
이 글에서는 SAP BTP ABAP Environment 및 S/4HANA 2022 이상(ABAP Language Version: ABAP for Cloud Development) 기준으로, CDS View Entity에서 SUM·COUNT·AVG 계열 집계 함수를 사용해 집계 뷰를 만들고, 이를 RAP 서비스로 노출한 뒤 OData V4의 $apply로 동적 group by까지 수행하는 전 과정을 판매 주문(Sales Order) 시나리오로 다룹니다.
- CDS View Entity의 GROUP BY와 집계 함수 문법 이해
- SUM / COUNT(DISTINCT) / AVG·MAX·MIN의 실전 사용법과 함정
- 집계 뷰를 RAP Service Definition·Binding으로 노출
- OData V4
$apply=groupby(...)쿼리 작성
2. CDS Aggregate View 기본 구조
집계 뷰의 뼈대는 SQL과 동일합니다. 집계 함수가 아닌 필드는 모두 GROUP BY에 포함되어야 하며, 이를 어기면 활성화 시점에 문법 오류가 발생합니다. 먼저 원천이 되는 판매 주문 인터페이스 뷰가 있다고 가정합니다.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Sales KPI - Basic Aggregate'
define view entity ZI_SalesKpiBasic
as select from zi_salesorder as so
{
key so.sales_organization as SalesOrganization,
key so.transaction_currency as TransactionCurrency,
@Semantics.amount.currencyCode: 'TransactionCurrency'
sum( so.net_amount ) as TotalNetAmount,
count( * ) as OrderCount
}
group by
so.sales_organization,
so.transaction_currency
핵심 규칙 두 가지를 기억하세요. 첫째, 통화·단위 필드는 반드시 GROUP BY에 포함해야 합니다. USD와 EUR 금액을 하나로 합산하면 의미 없는 숫자가 되기 때문에, @Semantics.amount.currencyCode 어노테이션과 함께 통화별로 그룹을 나누는 것이 안전합니다. 둘째, 집계 뷰는 키 정의가 곧 그룹 기준이 되므로 key 지정을 그룹 필드와 일치시키는 것이 일반적입니다.
3. SUM 함수로 매출 집계 구현
SUM은 가장 빈번하게 쓰이는 집계 함수입니다. 실무에서는 단순 합계보다 조건부 합계가 더 자주 필요합니다. CASE 식과 조합하면 "완료된 주문 매출"과 "취소된 주문 매출"을 한 번의 스캔으로 동시에 구할 수 있습니다.
define view entity ZI_SalesRevenueByOrg
as select from zi_salesorder as so
{
key so.sales_organization as SalesOrganization,
key so.transaction_currency as TransactionCurrency,
@Semantics.amount.currencyCode: 'TransactionCurrency'
sum( so.net_amount ) as GrossRevenue,
@Semantics.amount.currencyCode: 'TransactionCurrency'
sum( case when so.overall_status = 'C'
then so.net_amount
else 0 end ) as CompletedRevenue,
@Semantics.amount.currencyCode: 'TransactionCurrency'
sum( case when so.overall_status = 'X'
then so.net_amount
else 0 end ) as CancelledRevenue
}
group by
so.sales_organization,
so.transaction_currency
주의할 점은 오버플로입니다. 원본 필드가 abap.curr(15,2)라면 합산 결과가 자릿수를 초과할 수 있습니다. 대량 데이터라면 cast( so.net_amount as abap.dec(23,2) )처럼 캐스팅 후 합산하는 방어 코드가 권장됩니다.
4. COUNT와 COUNT DISTINCT 활용
COUNT는 두 가지 형태가 있습니다. count(*)는 그룹 내 행 수를, count( distinct 필드 )는 중복을 제거한 고유 값 개수를 반환합니다. "주문 건수"와 "구매 고객 수"는 전혀 다른 지표라는 점이 핵심입니다.
define view entity ZI_SalesCustomerStats
as select from zi_salesorder as so
{
key so.sales_organization as SalesOrganization,
count( * ) as TotalOrders,
count( distinct so.customer_id ) as UniqueCustomers,
sum( case when so.delivery_delayed = 'X'
then 1 else 0 end ) as DelayedOrders
}
group by
so.sales_organization
조건부 카운트는 SUM + CASE 패턴으로 구현하는 것이 정석입니다. COUNT 결과 타입은 abap.int8 계열로 결정되므로, 메타데이터가 중요한 소비자(Fiori 차트 등)를 위해 필요 시 cast(... as abap.int4)로 명시하기도 합니다. count( distinct )는 HANA에서도 상대적으로 비용이 큰 연산이므로, 카디널리티가 큰 컬럼에 남용하지 않는 것이 좋습니다.
5. AVG·MAX·MIN 함수와 주의사항
평균·최대·최소는 문법은 단순하지만 함정이 많습니다. 특히 CDS View Entity의 avg()는 반환 타입을 명시하는 AS 구문을 지원하며, 정수 필드 평균 시 소수점이 절사되는 문제를 피하려면 반드시 지정해야 합니다.
define view entity ZI_SalesOrderExtremes
as select from zi_salesorder as so
{
key so.sales_organization as SalesOrganization,
key so.transaction_currency as TransactionCurrency,
avg( so.net_amount as abap.dec(20,4) ) as AvgOrderValue,
@Semantics.amount.currencyCode: 'TransactionCurrency'
max( so.net_amount ) as LargestOrder,
@Semantics.amount.currencyCode: 'TransactionCurrency'
min( so.net_amount ) as SmallestOrder,
max( so.created_at ) as LatestOrderDate
}
group by
so.sales_organization,
so.transaction_currency
AVG는 NULL을 분모에서 제외하므로 "0 포함 평균"이 필요하면 sum(coalesce(x,0)) / count(*)를 직접 계산해야 합니다. MAX/MIN은 날짜·타임스탬프에도 적용 가능해 "최근 주문일" 같은 지표에 유용합니다.
6. RAP Business Object에 집계 뷰 연결
집계 뷰는 본질적으로 읽기 전용이므로, RAP에서는 Behavior Definition 없이 Query(조회 전용) 엔티티로 노출하는 것이 일반적입니다. Consumption 뷰를 한 겹 얹어 UI 어노테이션을 부여한 뒤 Service Definition에 등록합니다.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
@EndUserText.label: 'Sales KPI Query'
define view entity ZC_SalesKpiQuery
as projection on ZI_SalesRevenueByOrg
{
key SalesOrganization,
key TransactionCurrency,
GrossRevenue,
CompletedRevenue,
CancelledRevenue
}
@EndUserText.label: 'Sales KPI Service'
define service ZUI_SALES_KPI {
expose ZC_SalesKpiQuery as SalesKpi;
}
Service Binding에서 바인딩 타입을 OData V4 - UI 또는 OData V4 - Web API로 선택해 활성화하면 엔드포인트가 생성됩니다. 동적 집계를 쓰려면 OData V4가 필요합니다. 권한이 필요한 실환경에서는 #NOT_REQUIRED 대신 DCL(Access Control)을 작성해 조직 단위 필터를 거는 것이 안전합니다.
7. OData V4 $apply로 group by 쿼리
미리 GROUP BY를 고정한 집계 뷰와 달리, OData V4의 $apply는 소비자가 런타임에 그룹 기준을 선택할 수 있게 합니다. 이때는 집계 뷰가 아니라 라인 아이템 수준의 뷰를 노출하고, 쿼리에서 집계를 지시합니다.
-- 영업조직별 매출 합계 (동적 group by)
GET /SalesOrderItems
?$apply=groupby((SalesOrganization,TransactionCurrency),
aggregate(NetAmount with sum as SumAmount))
-- 필터 후 집계: 2026년 주문만
?$apply=filter(CreationDate ge 2026-01-01)
/groupby((CustomerId),
aggregate(NetAmount with sum as SumAmount,
$count as OrderCount))
-- 집계 결과에 다시 조건: 매출 상위 필터
?$apply=groupby((SalesOrganization),
aggregate(NetAmount with sum as SumAmount))
/filter(SumAmount gt 1000000)
filter → groupby → filter처럼 변환을 파이프(/)로 연결하는 것이 $apply의 강점이며, SQL의 WHERE → GROUP BY → HAVING에 대응합니다. Fiori Elements의 Analytical List Page는 내부적으로 이 쿼리를 생성합니다.
- Q. 집계 뷰 활성화 시 "field must be included in GROUP BY" 오류가 납니다. — 집계 함수 밖의 모든 프로젝션 필드는 GROUP BY 목록에 있어야 합니다. 통화·단위 참조 필드도 예외가 아닙니다.
- Q. $apply를 보냈는데 400 오류가 반환됩니다. — 바인딩이 OData V2이거나, 집계가 지원되지 않는 엔티티일 수 있습니다. V4 바인딩 여부와
@Aggregation.default설정을 확인하세요. - Q. 통화가 다른 금액이 합쳐져 이상한 값이 나옵니다. — groupby 그룹 필드에 통화 코드를 반드시 포함시키거나,
currency_conversion으로 기준 통화 통일 후 집계해야 합니다.
8. 실전 시나리오: 판매 대시보드 집계
세 가지 접근을 조합한 실전 구성입니다. (1) 고정 KPI 카드에는 ZI_SalesRevenueByOrg 같은 사전 집계 뷰를, (2) 사용자가 축을 바꾸는 차트에는 라인 아이템 뷰 + $apply를, (3) 상세 드릴다운에는 트랜잭션 BO를 사용합니다. 성능 관점에서는 사전 집계 뷰가 실행 계획이 안정적이고, $apply는 유연성이 높은 대신 인덱스 설계에 민감하므로, 대시보드 초기 로딩 지표는 사전 집계로 고정하는 전략이 일반적으로 권장됩니다. 집계 뷰의 검증은 ADT의 Data Preview와 SQL 콘솔로 기대값을 대조하고, CDS Test Double Framework(cl_cds_test_environment)로 단위 테스트를 작성하면 회귀를 방지할 수 있습니다.
댓글 0
아직 댓글이 없습니다.