이 글의 목적과 핵심 점검 항목
ABAP 7.4부터 도입된 FOR ... IN 표현식은 명령형(Imperative) 스타일의 LOOP AT ... APPEND 패턴을 선언형(Declarative) 한 줄로 압축해 주는 강력한 도구입니다. 이 글에서는 단순 변환부터 필터링·중첩·구조 매핑까지 실무에서 자주 만나는 시나리오를 단계별로 다루며, 코드를 짧게 만드는 것이 아니라 "의도가 드러나는 코드"를 작성하는 방법을 익히는 것을 목표로 합니다.
- LOOP-APPEND 패턴이 만들어내는 가독성·유지보수 비용 이해
- FOR ... IN의 기본 문법과 컨텍스트 변수의 동작 원리
- WHERE / WHILE / UNTIL 조건의 차이와 사용처
- 중첩 FOR, FOR ... GROUPS BY를 활용한 복잡한 변환
- VALUE 생성자 결합 시 메모리·성능 트레이드오프
읽기 전에 알고 있으면 좋은 것들
이 글은 ABAP 내부 테이블(Standard / Sorted / Hashed Table), 인라인 선언(DATA(...)), VALUE 생성자 표현식에 대한 기초 지식을 전제로 합니다. 또한 구조체(Structure)와 워크에어리어(Work Area) 개념, READ TABLE 또는 line_index의 기본 동작을 이해하고 있다면 본문의 비교 예시를 더 쉽게 따라올 수 있습니다.
환경, 릴리스, 준비물
FOR ... IN 표현식은 ABAP 7.40 SP05에서 처음 도입되었고, 7.40 SP08 이후 GROUPS BY·REFERENCE INTO 등 확장 문법이 추가되었습니다. 본 예제는 다음 환경에서 검증하는 것을 일반적으로 권장합니다.
- SAP NetWeaver AS ABAP 7.50 이상 (또는 ABAP Platform 2022 / S/4HANA 2022 on-premise)
- ABAP Cloud (BTP ABAP Environment, Steampunk) — RAP 컨텍스트에서도 동일하게 사용 가능
- ABAP Development Tools (ADT) on Eclipse 2024-03 이상 권장
- 최신 문법 체크를 위해
strict모드 또는 ABAP Cleaner 활성화
S/4HANA Cloud Public Edition의 ABAP RESTful Application Programming Model(RAP) 환경에서도 동일한 표현식이 동작하지만, 일부 시스템 함수(line_index 등)는 제한될 수 있으므로 릴리스 노트를 함께 확인하는 것이 안전합니다.
왜 LOOP를 표현식으로 바꾸어야 하는가
전통적인 ABAP에서는 한 테이블의 데이터를 다른 모양으로 옮길 때 거의 반드시 LOOP AT ... INTO ... APPEND ... TO ... 패턴을 사용했습니다. 이 패턴은 직관적이지만 다음과 같은 비용을 동반합니다.
- 의도와 구현의 분리: "이 리스트에서 저 리스트로 변환한다"는 의도가 LOOP, 워크에어리어, APPEND 세 줄로 흩어진다.
- 중간 변수 오염: 워크에어리어(
wa_)가 LOOP 외부에 남아 다른 로직에서 잘못 참조될 위험. - 변경 가능 상태(mutable state): 결과 테이블이 점진적으로 변하기 때문에 부분 실행, 병렬화, 단위 테스트가 어려워진다.
FOR ... IN은 이러한 문제를 함수형(Functional) 패러다임의 "테이블 컴프리헨션(Table Comprehension)"으로 해결합니다. 비유하자면 SQL의 SELECT col FROM source WHERE cond를 ABAP 메모리 내 테이블에 적용하는 것과 같습니다. VALUE 생성자가 "어떤 타입의 결과를 만들지"를 선언하고, FOR가 "어디서 가져올지"를, WHERE가 "어떤 조건만 통과시킬지"를, 매핑식이 "어떻게 변형할지"를 한 표현식 안에서 끝냅니다.
도식으로 보면
VALUE result_type( FOR row IN source [WHERE ...] ( mapping ) )구조는 좌측에서 우측으로 "타입 → 입력 → 필터 → 출력"이 자연스럽게 흐릅니다. 이는 한 줄 안에서 코드 리뷰어가 데이터 흐름을 시각적으로 추적할 수 있게 해 줍니다.
중요한 점은 FOR로 선언되는 변수(예시의 row)가 표현식 안에서만 유효한 로컬 컨텍스트 변수라는 것입니다. 표현식 종료 후에는 접근할 수 없으며, 외부 변수와 동일한 이름을 써도 충돌하지 않습니다. 이 캡슐화 덕분에 LOOP에서 흔히 발생하던 "워크에어리어 누수" 문제가 원천 차단됩니다.
1단계: 단순 매핑부터 시작하기
가장 자주 만나는 시나리오는 "수주 테이블에서 특정 필드만 뽑아 요약 테이블을 만든다"입니다. LOOP-APPEND 방식과 FOR ... IN 방식을 나란히 비교해 봅니다.
TYPES: BEGIN OF ty_sales_order,
order_id TYPE c LENGTH 10,
customer_id TYPE c LENGTH 8,
net_amount TYPE p LENGTH 11 DECIMALS 2,
currency TYPE c LENGTH 3,
created_on TYPE d,
END OF ty_sales_order,
tt_sales_order TYPE STANDARD TABLE OF ty_sales_order WITH EMPTY KEY.
TYPES: BEGIN OF ty_order_summary,
order_id TYPE c LENGTH 10,
net_amount TYPE p LENGTH 11 DECIMALS 2,
END OF ty_order_summary,
tt_order_summary TYPE STANDARD TABLE OF ty_order_summary WITH EMPTY KEY.
DATA(lt_orders) = VALUE tt_sales_order(
( order_id = 'SO-0001' customer_id = 'C0001' net_amount = '1200.00' currency = 'EUR' created_on = '20260601' )
( order_id = 'SO-0002' customer_id = 'C0002' net_amount = '350.50' currency = 'USD' created_on = '20260602' )
( order_id = 'SO-0003' customer_id = 'C0001' net_amount = '4500.00' currency = 'EUR' created_on = '20260603' ) ).
" 전통적인 방식
DATA lt_summary_old TYPE tt_order_summary.
DATA ls_summary TYPE ty_order_summary.
LOOP AT lt_orders INTO DATA(ls_order).
ls_summary-order_id = ls_order-order_id.
ls_summary-net_amount = ls_order-net_amount.
APPEND ls_summary TO lt_summary_old.
ENDLOOP.
" FOR ... IN 방식
DATA(lt_summary_new) = VALUE tt_order_summary(
FOR <order> IN lt_orders
( order_id = <order>-order_id
net_amount = <order>-net_amount ) ).
두 결과는 동일하지만 두 번째 버전은 중간 변수 ls_summary가 사라졌고, "수주 테이블의 각 행에서 두 필드만 골라 요약을 만든다"는 의도가 표현식 자체에서 읽힙니다. <order>는 필드 심볼(Field Symbol)로 선언되어 복사 비용이 없으며, 일반적으로 성능면에서도 권장됩니다.
2단계: 필터링·에러 처리·로깅이 섞인 실무 시나리오
실무에서는 "특정 통화의 일정 금액 이상 수주만 추출하고, 변환 중 누락된 고객 ID는 로그로 남긴다"와 같은 요구가 흔합니다. WHERE 절과 COND / SWITCH 표현식을 결합해 처리할 수 있습니다.
TYPES: BEGIN OF ty_invoice_line,
invoice_no TYPE c LENGTH 10,
order_ref TYPE c LENGTH 10,
customer_id TYPE c LENGTH 8,
amount_local TYPE p LENGTH 13 DECIMALS 2,
risk_flag TYPE abap_bool,
END OF ty_invoice_line,
tt_invoice_line TYPE STANDARD TABLE OF ty_invoice_line WITH EMPTY KEY.
CONSTANTS: c_threshold TYPE p LENGTH 11 DECIMALS 2 VALUE '1000.00',
c_target_currency TYPE c LENGTH 3 VALUE 'EUR'.
DATA lt_audit_log TYPE STANDARD TABLE OF string.
TRY.
DATA(lt_invoices) = VALUE tt_invoice_line(
FOR <ord> IN lt_orders
WHERE ( currency = c_target_currency
AND net_amount >= c_threshold )
( invoice_no = |INV-{ <ord>-order_id+3(*) }|
order_ref = <ord>-order_id
customer_id = <ord>-customer_id
amount_local = <ord>-net_amount
risk_flag = COND #( WHEN <ord>-net_amount > 4000
THEN abap_true
ELSE abap_false ) ) ).
" 변환 결과 검증 및 로깅
IF lt_invoices IS INITIAL.
APPEND |No EUR invoice generated above { c_threshold }| TO lt_audit_log.
ENDIF.
CATCH cx_sy_itab_error INTO DATA(lx_itab).
APPEND |FOR expression failed: { lx_itab->get_text( ) }| TO lt_audit_log.
ENDTRY.
주의할 점은 WHERE 절은 SELECT ... WHERE와 비슷한 표현식 일부만 허용한다는 것입니다. 비교 연산, 논리 결합(AND, OR, NOT), IS INITIAL·BETWEEN·IN 등은 사용할 수 있지만, 사용자 정의 메서드 호출은 직접 지원되지 않으므로 그런 경우엔 다음 단계에서 다룰 REDUCE 또는 사전 필터링을 결합해야 합니다.
3단계: 프로덕션급 패턴 - 그룹핑, 중첩, 성능 튜닝
여러 수주 행을 고객 단위로 합산해 청구 요약을 만드는 시나리오를 봅시다. GROUP BY가 결합된 FOR는 LOOP 기반 그룹 처리 로직을 한 표현식으로 대체합니다.
TYPES: BEGIN OF ty_customer_total,
customer_id TYPE c LENGTH 8,
total_amount TYPE p LENGTH 15 DECIMALS 2,
order_count TYPE i,
END OF ty_customer_total,
tt_customer_total TYPE STANDARD TABLE OF ty_customer_total WITH EMPTY KEY.
DATA(lt_customer_totals) = VALUE tt_customer_total(
FOR GROUPS <grp> OF <ord> IN lt_orders
GROUP BY ( customer_id = <ord>-customer_id
currency = <ord>-currency )
ASCENDING
( customer_id = <grp>-customer_id
total_amount = REDUCE p( INIT s TYPE p LENGTH 15 DECIMALS 2 VALUE 0
FOR <m> IN GROUP <grp>
NEXT s = s + <m>-net_amount )
order_count = REDUCE i( INIT c = 0
FOR <m> IN GROUP <grp>
NEXT c = c + 1 ) ) ).
여기서 FOR GROUPS는 LOOP의 AT NEW / AT END OF를 대체하며, 각 그룹 내부 행을 FOR ... IN GROUP으로 다시 순회합니다. REDUCE는 누산 결과를 만들어 주는 표현식으로 SUM·COUNT·MAX 등 다양한 집계를 표현할 수 있습니다.
프로덕션 환경에서 다음 가이드를 고려하면 성능 리스크를 줄일 수 있습니다.
- 필드 심볼 사용:
FOR wa IN tab보다FOR <fs> IN tab이 복사 오버헤드가 적어 대용량 테이블에서 유리합니다. - 중첩 FOR 주의:
FOR a IN tab1 FOR b IN tab2 WHERE b-key = a-key형태의 카르테시안 결합은 O(N×M)이므로, 한쪽을 SORTED/HASHED 테이블로 미리 변환하거나 SQL 단에서 조인 후 가져오는 편이 일반적으로 권장됩니다. - 예외 안전성: 표현식 내부에서 0으로 나누기, 타입 변환 실패 등이 발생하면 전체 변환이 롤백되지 않고 중단됩니다. 부분 실패를 허용하려면
COND로 방어값을 채우거나 사전에 정제 단계를 두는 것이 좋습니다. - 단위 테스트: 표현식은 입력 → 출력의 순수 변환이므로 ABAP Unit에서 입력 테이블만 준비하면 결과를 결정적으로 검증할 수 있어 테스트 친화적입니다.
자주 발생하는 실수와 빠른 진단
Q1. "FOR 표현식에서 결과가 비어 나옵니다."
가장 흔한 원인은 WHERE 절의 데이터 타입 불일치입니다. 예를 들어 currency = 'eur'처럼 소문자 리터럴을 비교하면 대문자로 저장된 실제 값과 매칭되지 않습니다. WHERE 절은 자동 대소문자 변환을 하지 않으므로 입력 정제가 먼저입니다.
Q2. "워크에어리어를 외부에서 참조했더니 신택스 오류가 납니다."
FOR <row> IN tab의 <row>는 표현식 내부 스코프 변수입니다. 표현식이 끝나면 더 이상 존재하지 않으며, 같은 이름의 외부 필드 심볼이 있어도 가려집니다. 변환 후 후속 처리가 필요하면 결과 테이블을 받아 다시 LOOP하거나 다음 FOR 표현식으로 체이닝해야 합니다.
Q3. "FOR ... GROUPS BY가 정렬되지 않은 결과를 돌려줍니다."
GROUP BY 자체는 정렬을 보장하지 않습니다. ASCENDING 또는 DESCENDING 키워드를 명시하거나, 결과 테이블을 SORTED TABLE로 선언하면 안정적인 순서를 얻을 수 있습니다.
Q4. "LOOP보다 FOR이 항상 빠른가요?"
일반적으로 비슷하거나 약간 빠른 정도입니다. 컴파일러가 내부적으로 LOOP 코드로 변환하기 때문에 극적인 성능 차이는 기대하기 어렵습니다. FOR을 선택하는 이유는 성능보다 가독성·불변성·테스트 용이성에 있다는 점을 팀에 공유하는 것이 좋습니다.
Q5. "WHERE에 메서드 호출을 넣고 싶습니다."
직접 지원되지 않습니다. 미리 메서드 결과를 보조 컬럼으로 만들어 두거나, FILTER 연산자 또는 사전 LOOP로 분리하는 패턴을 권장합니다.
이 글을 마치고 살펴보면 좋은 주제들
FOR ... IN은 ABAP 함수형 표현식 가족(VALUE, COND, SWITCH, REDUCE, FILTER, CORRESPONDING)의 한 축입니다. 다음 주제로 확장해 가면 표현식 기반 ABAP 스타일 전반을 갖출 수 있습니다.
REDUCE를 활용한 집계·누산 패턴 심화FILTER연산자와 SORTED/HASHED 테이블 조합CORRESPONDING #( ... MAPPING ... )로 필드 매핑 자동화- RAP Behavior Implementation 안에서 FOR 표현식으로 결과 셋 가공
- ABAP Cleaner / ATC 규칙으로 LOOP-APPEND 패턴을 자동 리팩터링
더 깊이 파고들 때 도움이 되는 자료
- SAP Help Portal — Iterative Expressions (ABAP Keyword Documentation)
- SAP Help Portal — FOR, Iteration Expressions
- SAP Help Portal — Constructor Expressions 개요
- SAP Help Portal — ABAP Platform Documentation
- SAP Community — ABAP 토픽 (실전 Q&A와 코드 패턴 토론)
- SAP Clean ABAP Style Guide (GitHub) — 표현식 사용 가이드
- SAP Blogs — ABAP Language 태그 (릴리스 노트 및 사례)
댓글 0
아직 댓글이 없습니다.