개요와 이 글에서 얻을 것
ABAP 7.40 이후 도입된 REDUCE는 컬렉션을 단일 값으로 축약(reduce)하는 표현식 기반 구문입니다. 과거에는 합계·최대값·문자열 병합을 위해 DATA 선언 → LOOP AT → 누산 → ENDLOOP 4단계를 거쳐야 했지만, REDUCE는 이 모든 과정을 한 줄의 표현식으로 대체합니다. 이 글에서는 SalesOrder 금액 합산, PurchaseRequisition 최대 수량, 코드-라벨 매핑까지 실무에서 자주 마주치는 시나리오를 통해 FOR ... THEN ... UNTIL 패턴을 완전히 익히는 것을 목표로 합니다.
- REDUCE 표현식의 문법 구조와 등장 배경을 이해한다
- FOR THEN 반복 패턴을 자유롭게 응용한다
- 합계·최대값·문자열 집계를 한 줄로 표현한다
- 중첩 REDUCE 및 성능 관점에서의 주의사항을 파악한다
알고 있어야 할 배경
ABAP 7.40 SP08 이상 시스템에서 표현식 기반 구문(VALUE, NEW, CORRESPONDING, FOR)을 다뤄본 경험이 있으면 좋습니다. 또한 인터널 테이블 선언과 LOOP AT·READ TABLE의 기본 동작 원리를 이해하고 있어야 REDUCE가 왜 간결한지 체감할 수 있습니다.
테스트 환경과 준비물
이 글의 예제는 다음 환경에서 동작을 검증했습니다.
- SAP NetWeaver AS ABAP 7.52 이상 (S/4HANA 1909, 2020, 2022, 2023 모두 호환)
- ADT(ABAP Development Tools) in Eclipse 2023-12 이상 권장
- 표현식 기반 구문은 7.40 SP08부터 지원되나,
REDUCE는 7.40 SP08+ 부터 안정적으로 사용 가능 - 테스트용 인터널 테이블 데이터를
VALUE #( ... )로 인라인 생성
ABAP Cloud(Steampunk) 및 RAP 시나리오에서도 REDUCE는 그대로 사용 가능하며, ABAP for Cloud Development 규칙(제한된 명령어)에도 위반되지 않습니다.
핵심 개념: REDUCE와 FOR THEN
REDUCE는 이름 그대로 "여러 값을 하나로 접는(fold) 표현식"입니다. 함수형 언어의 fold/reduce와 동일한 개념으로, 초기값에서 시작해 컬렉션의 원소를 순회하며 결과를 누적합니다.
기본 구조는 다음과 같습니다.
REDUCE result_type(
INIT accumulator = 초기값
FOR wa IN itab
NEXT accumulator = 누적_표현식
)여기서 INIT은 누산 변수의 시작 상태, FOR는 순회 대상 컬렉션, NEXT는 매 반복마다 누산 변수를 어떻게 갱신할지 정의합니다. 마치 은행 창구에서 통장(INIT) 잔액에 매일 들어오는 거래(FOR) 금액을 더해(NEXT) 최종 잔액을 만드는 것과 동일한 흐름입니다.
FOR THEN UNTIL/WHILE 패턴은 컬렉션이 아니라 "수열(sequence)"을 만들 때 쓰입니다. 형태는 아래와 같습니다.
FOR i = start THEN i + step UNTIL i > limitTHEN 절이 다음 iteration의 값을 만드는 규칙이고, UNTIL 또는 WHILE이 종료 조건입니다. 이 패턴을 REDUCE의 FOR 위치에 얹으면, 인덱스 기반 순회·수열 합산·수학적 반복까지 한 줄로 표현할 수 있습니다.
비유하자면 REDUCE는 "누산 통", FOR IN은 "장바구니를 하나씩 꺼내는 손", FOR THEN은 "카운터를 규칙에 따라 튕기는 손"입니다. 세 요소가 조합되면 대부분의 집계 로직이 표현식 하나로 압축됩니다.
1단계: 합계 구하기 - SalesOrder 금액 합산
가장 자주 만나는 패턴부터 시작합니다. 판매주문 헤더의 금액을 모두 더해 총매출을 계산하는 예제입니다.
TYPES: BEGIN OF ty_sales_order,
order_id TYPE c LENGTH 10,
net_amount TYPE p LENGTH 13 DECIMALS 2,
currency TYPE c LENGTH 3,
END OF ty_sales_order,
tt_sales_order TYPE STANDARD TABLE OF ty_sales_order WITH EMPTY KEY.
DATA(lt_orders) = VALUE tt_sales_order(
( order_id = SO0000001 net_amount = 1250.00 currency = EUR )
( order_id = SO0000002 net_amount = 3480.75 currency = EUR )
( order_id = SO0000003 net_amount = 920.40 currency = EUR )
( order_id = SO0000004 net_amount = 15600.00 currency = EUR ) ).
" REDUCE 스타일 - 한 줄
DATA(lv_total) = REDUCE p( INIT sum TYPE p LENGTH 15 DECIMALS 2
FOR <wa> IN lt_orders
NEXT sum = sum + <wa>-net_amount ).
WRITE: / |총 매출: { lv_total NUMBER = USER } EUR|.INIT에서 누산 변수 sum의 타입을 p LENGTH 15 DECIMALS 2로 명시한 이유는 개별 주문 금액보다 총합이 커질 수 있어 오버플로 방지를 위해서입니다. 초기값을 생략하면 해당 타입의 기본값(숫자형이면 0)이 사용됩니다.
2단계: 최대값과 조건부 집계 - PurchaseRequisition 수량
실무에서는 단순 합계보다 "특정 조건을 만족하는 원소 중 최대값" 같은 요구가 훨씬 많습니다. 구매요청 아이템 중 승인 완료(APPROVED) 상태의 요청 수량 최대값을 구하는 시나리오입니다.
DATA(lv_max_qty) = REDUCE p( INIT max TYPE p LENGTH 13 DECIMALS 3 VALUE 0
FOR <pr> IN lt_pr WHERE ( status = APPROVED )
NEXT max = COND #( WHEN <pr>-quantity > max
THEN <pr>-quantity
ELSE max ) ).
WRITE: / |승인된 PR 중 최대 수량: { lv_max_qty NUMBER = USER }|.여기서 세 가지 새 문법이 결합됐습니다. FOR ... IN ... WHERE로 순회 대상을 필터링하고, COND #(...)로 조건부 값을 만든 뒤, NEXT로 누산합니다. WHERE 조건에 해당하는 원소가 하나도 없다면 INIT에서 지정한 초기값(0)이 그대로 반환되므로, 예외적 상황에 대한 방어 코드가 별도로 필요 없습니다.
3단계: 문자열 병합, FOR THEN 수열, 중첩 REDUCE
프로덕션에서는 단순 숫자 집계보다 "코드를 라벨로 치환해 리포트 문자열 만들기", "구간별 세금 누계 계산", "2차원 데이터 합계" 같은 복합 로직이 훨씬 흔합니다.
" (A) 상태 코드 -> 한국어 라벨 매핑 후 CSV 병합
DATA(lv_csv) = REDUCE string(
INIT out = ``
FOR <pr> IN lt_pr
NEXT out = |{ out }{ COND #( WHEN out IS INITIAL THEN `` ELSE `, ` ) }| &&
VALUE #( lt_map[ code = <pr>-status ]-label DEFAULT <pr>-status ) ).
" (B) FOR THEN 패턴 - 1부터 100까지 짝수 합
DATA(lv_even_sum) = REDUCE i( INIT s = 0
FOR i = 2 THEN i + 2 UNTIL i > 100
NEXT s = s + i ).
" 결과: 2550
" (C) 중첩 REDUCE - PR 번호별 승인 수량 총합
DATA(lt_summary) = VALUE STANDARD TABLE OF ty_pr_total WITH EMPTY KEY(
FOR <pr_no> IN lt_distinct
( pr_number = <pr_no>
total_qty = REDUCE p( INIT t TYPE p LENGTH 13 DECIMALS 3
FOR <i> IN lt_pr
WHERE ( pr_number = <pr_no> AND status = APPROVED )
NEXT t = t + <i>-quantity ) ) ).(A)에서 lt_map[ code = ... ]-label DEFAULT ...는 매핑이 없을 때 원본 코드를 보여주도록 하는 방어 표현입니다. (B)의 FOR THEN UNTIL은 인터널 테이블 없이도 순수 수열 반복이 가능함을 보여줍니다. (C)는 VALUE 구성자 안에 REDUCE를 중첩해, SQL의 GROUP BY SUM을 표현식으로 재현한 형태입니다.
흔한 실수와 트러블슈팅 FAQ
Q1. INIT 뒤 변수 타입이 결정되지 않는다는 문법 오류가 납니다.
사용자 정의 구조나 packed 형은 INIT sum TYPE p LENGTH 15 DECIMALS 2처럼 TYPE을 명시해야 합니다. #(inferred type)를 쓰면 컴파일러가 대상 타입을 추론하지 못해 오류가 납니다.
Q2. WHERE 조건에 해당하는 원소가 없으면 예외가 발생하나요?
아니요. NEXT가 한 번도 실행되지 않고 INIT 값이 그대로 반환됩니다. REDUCE 내부에서 다른 테이블을 조회할 때는 VALUE #( ... DEFAULT ... ) 형태의 방어 표현을 권장합니다.
Q3. 성능이 걱정됩니다. LOOP보다 느리진 않나요?
일반적으로 REDUCE는 내부적으로 LOOP AT과 동등한 코드로 변환됩니다. 수십만 건 이상의 대용량 데이터에서는 SORTED/HASHED 테이블 활용이나 DB 집계 함수(SELECT SUM(...))로 대체하는 것이 더 효율적입니다.
Q4. 디버깅이 어렵습니다.
표현식 내부에는 브레이크포인트를 걸 수 없으므로, 문제가 있을 땐 임시로 LOOP로 풀어서 검증하거나, NEXT 표현식의 복잡한 로직을 별도 메서드로 분리해 그 메서드에 브레이크포인트를 거는 방식을 사용합니다.
이어서 익히면 좋은 표현식 조합
REDUCE에 익숙해졌다면 VALUE·FILTER·CORRESPONDING 표현식과의 조합으로 관심 범위를 넓혀보길 권합니다. 컬렉션 필터링은 FILTER가, 구조 변환은 CORRESPONDING이, 리스트 생성은 VALUE + FOR가 담당하므로 네 가지를 조합하면 대부분의 데이터 파이프라인이 표현식만으로 완성됩니다. 이후에는 CDS View의 aggregate 함수, RAP의 determinations·validations, AMDP에서의 집계 처리로 확장해 애플리케이션 서버-DB 간 역할 분담을 최적화하는 것을 권장합니다.
댓글 0
아직 댓글이 없습니다.