VALUE
03. New Syntax 정리에서 VALUE #와 FOR를 가볍게 다뤘습니다. 이 글은 그중 FOR 반복과 그룹 구문(FOR GROUPS ... OF ... IN / FOR ... IN GROUP)만 떼어 예제 중심으로 깊게 봅니다.
예제에 쓸 데이터
1TYPES: BEGIN OF ty_item,
2 werks TYPE werks_d,
3 matnr TYPE matnr,
4 menge TYPE menge_d,
5 netwr TYPE netwr,
6 END OF ty_item.
7TYPES tt_item TYPE STANDARD TABLE OF ty_item WITH EMPTY KEY.
8
9DATA(lt_item) = VALUE tt_item(
10 ( werks = '1000' matnr = 'M-01' menge = 10 netwr = 1000 )
11 ( werks = '1000' matnr = 'M-01' menge = 5 netwr = 500 )
12 ( werks = '1000' matnr = 'M-02' menge = 7 netwr = 700 )
13 ( werks = '2000' matnr = 'M-01' menge = 3 netwr = 300 ) ).
FOR - 테이블을 돌며 새 테이블 만들기
LOOP ... APPEND ... ENDLOOP 한 덩어리를 한 줄로 바꿉니다.
1TYPES tt_matnr TYPE STANDARD TABLE OF matnr WITH EMPTY KEY.
2
3
4DATA lt_matnr TYPE tt_matnr.
5LOOP AT lt_item INTO DATA(ls_old).
6 APPEND ls_old-matnr TO lt_matnr.
7ENDLOOP.
8
9
10DATA(lt_matnr2) = VALUE tt_matnr( FOR ls IN lt_item ( ls-matnr ) ).
FOR ls IN lt_item이 루프, 뒤의 ( ... )가 만들어 넣을 행입니다. ls는 따로 선언하지 않아도 됩니다.
WHERE로 거르기
1DATA(lt_1000) = VALUE tt_item( FOR ls IN lt_item WHERE ( werks = '1000' ) ( ls ) ).
조건은 괄호 안에 씁니다. 필드명은 ls-werks가 아니라 werks로 적는다는 점에 주의하세요.
숫자를 세며 돌기
1TYPES tt_int TYPE STANDARD TABLE OF i WITH EMPTY KEY.
2
3DATA(lt_num) = VALUE tt_int( FOR i = 1 THEN i + 1 UNTIL i > 5 ( i ) ).
4
THEN이 증가 규칙, UNTIL이 종료 조건입니다. WHILE을 쓰면 계속할 조건이 됩니다.
LET으로 중간값 두기
1TYPES: BEGIN OF ty_calc,
2 matnr TYPE matnr,
3 netwr TYPE netwr,
4 vat TYPE netwr,
5 END OF ty_calc.
6TYPES tt_calc TYPE STANDARD TABLE OF ty_calc WITH EMPTY KEY.
7
8DATA(lt_calc) = VALUE tt_calc(
9 FOR ls IN lt_item
10 LET tax = ls-netwr / 10 IN
11 ( matnr = ls-matnr netwr = ls-netwr vat = tax ) ).
같은 계산을 여러 번 쓸 때 LET으로 한 번만 계산해 두면 읽기도 쉬워집니다.
그룹 구문 - 두 가지를 먼저 구분하자
이름이 비슷해서 가장 많이 헷갈리는 지점입니다. GROUPS OF가 들어간 것과 IN GROUP이 들어간 것은 완전히 다른 구문입니다.
그룹을 만든다FOR GROUPS ... OF ... IN itab테이블을 키로 묶어 그룹 단위로 반복합니다. 한 바퀴가 한 그룹입니다.
그룹 안을 돈다FOR ... IN GROUP grp이미 만들어진 그룹의 멤버 행을 반복합니다. 한 바퀴가 한 행입니다.
헷갈리지 않으려면 두 축으로 나눠 보면 됩니다. GROUPS OF가 있으면 그룹을 돈다는 뜻이고, IN 뒤에 오는 것이 어디서 도는지를 말합니다.
| IN itab (테이블에서) | IN GROUP grp (그룹 안에서) |
|---|
행을 돈다
FOR wa | FOR ls IN lt_item | FOR x IN GROUP grp |
그룹을 돈다
FOR GROUPS ... OF wa | FOR GROUPS grp OF ls IN lt_item | FOR GROUPS sub OF x IN GROUP grp (그룹 안에서 또 묶기) |
왼쪽 위가 앞에서 본 평범한 FOR이고, 오른쪽 아래는 거의 쓸 일이 없는 중첩 그룹핑입니다. 실무에서 쓰는 건 왼쪽 아래로 그룹을 만들고, 오른쪽 위로 그 안을 도는 조합입니다.
그룹 만들기 - FOR GROUPS ... OF ... IN
문법은 이렇습니다.
1FOR GROUPS [그룹변수] OF 작업영역 IN 내부테이블
2 [INDEX INTO 인덱스] [조건]
3 GROUP BY 그룹키
4 [ASCENDING|DESCENDING [AS TEXT]]
5 [WITHOUT MEMBERS]
6 [LET ...]
바인딩 방식이 두 가지다
그룹변수를 쓰느냐 마느냐에 따라 꺼내 쓸 수 있는 것이 달라집니다.
그룹키 바인딩FOR GROUPS grp OF ls IN lt_itemgrp에 그룹 키가 담깁니다. 이때 작업영역 ls는 비어 있어 직접 쓸 수 없습니다.
대표행 바인딩FOR GROUPS OF ls IN lt_item그룹변수를 생략하면 ls에 각 그룹의 첫 번째 행이 담깁니다. 그 행의 아무 필드나 꺼내 쓸 수 있습니다.
1TYPES: BEGIN OF ty_key,
2 werks TYPE werks_d,
3 matnr TYPE matnr,
4 END OF ty_key.
5TYPES tt_key TYPE STANDARD TABLE OF ty_key WITH EMPTY KEY.
6
7
8DATA(lt_key) = VALUE tt_key(
9 FOR GROUPS grp OF ls IN lt_item
10 GROUP BY ( werks = ls-werks matnr = ls-matnr )
11 ( werks = grp-werks matnr = grp-matnr ) ).
12
13
14DATA(lt_first) = VALUE tt_item(
15 FOR GROUPS OF ls IN lt_item
16 GROUP BY ( werks = ls-werks matnr = ls-matnr )
17 ( ls ) ).
②는 SORT + DELETE ADJACENT DUPLICATES를 정렬 없이 해내는 셈이라 꽤 유용합니다.
WITHOUT MEMBERS - 멤버를 안 볼 거면 명시한다
그룹 안의 개별 행을 쓰지 않고 키만 필요할 때 붙입니다.
1DATA(lt_key2) = VALUE tt_key(
2 FOR GROUPS grp OF ls IN lt_item
3 GROUP BY ( werks = ls-werks matnr = ls-matnr )
4 WITHOUT MEMBERS
5 ( werks = grp-werks matnr = grp-matnr ) ).
규칙이 둘 있습니다.
WARNING
WITHOUT MEMBERS는 그룹변수를 지정했을 때만 쓸 수 있습니다. FOR GROUPS OF ls IN ...처럼 대표행 바인딩에는 붙이지 못합니다.
그리고 이걸 붙인 그룹에는
뒤에 나올 FOR ... IN GROUP을 쓸 수 없습니다. 멤버를 들고 있지 않기 때문입니다.
정렬
1FOR GROUPS grp OF ls IN lt_item
2GROUP BY ( werks = ls-werks )
3ASCENDING
TIP
ASCENDING / DESCENDING을 붙이지 않았을 때 그룹이 어떤 순서로 나오는지는 보장된다고 생각하지 마세요. 순서가 의미를 갖는다면 명시하거나, 결과를 받은 뒤 SORT하는 편이 안전합니다.
그룹 안을 돌기 - FOR ... IN GROUP
앞에서 만든 그룹의 멤버 행을 순회하는 구문입니다. 문법은 평범한 FOR ... IN itab과 거의 같고, 대상이 테이블이 아니라 그룹이라는 점만 다릅니다.
1FOR 작업영역 IN GROUP 그룹변수 [INDEX INTO 인덱스] [WHERE (조건)]
쓸 수 있는 자리가 정해져 있습니다. 그룹에 접근할 수 있는 곳, 즉 FOR GROUPS ... OF ... 안이나 LOOP AT ... GROUP BY 안에서만 쓸 수 있습니다.
그룹별 합계가 대표적인 활용입니다.
1TYPES: BEGIN OF ty_sum,
2 werks TYPE werks_d,
3 matnr TYPE matnr,
4 menge TYPE menge_d,
5 netwr TYPE netwr,
6 END OF ty_sum.
7TYPES tt_sum TYPE STANDARD TABLE OF ty_sum WITH EMPTY KEY.
8
9DATA(lt_sum) = VALUE tt_sum(
10 FOR GROUPS grp OF ls IN lt_item
11 GROUP BY ( werks = ls-werks matnr = ls-matnr )
12 ( werks = grp-werks
13 matnr = grp-matnr
14 menge = REDUCE menge_d( INIT m = 0
15 FOR x IN GROUP grp
16 NEXT m = m + x-menge )
17 netwr = REDUCE netwr( INIT n = 0
18 FOR x IN GROUP grp
19 NEXT n = n + x-netwr ) ) ).
20
21
22
23
바깥쪽 FOR GROUPS grp OF ls IN lt_item이 그룹을 만들고, 안쪽 FOR x IN GROUP grp가 그 그룹의 멤버를 돕니다. 두 구문이 짝으로 쓰이는 전형적인 모습입니다. REDUCE가 멤버를 돌며 누적하므로, SQL의 GROUP BY + SUM()과 같은 결과가 나옵니다.
WHERE로 멤버 일부만 골라 집계할 수도 있습니다.
1
2menge = REDUCE menge_d( INIT m = 0
3 FOR x IN GROUP grp WHERE ( werks = '1000' )
4 NEXT m = m + x-menge )
다른 테이블의 값을 끌어오기
실무에서 가장 많이 쓰는 조합입니다. 묶으면서 동시에 다른 테이블에서 값을 찾아 붙이는 패턴입니다.
1TYPES: BEGIN OF ty_text,
2 matnr TYPE matnr,
3 maktx TYPE maktx,
4 END OF ty_text.
5TYPES tt_text TYPE SORTED TABLE OF ty_text WITH UNIQUE KEY matnr.
6
7DATA(lt_text) = VALUE tt_text(
8 ( matnr = 'M-01' maktx = '볼트' )
9 ( matnr = 'M-02' maktx = '너트' ) ).
10
11TYPES: BEGIN OF ty_out,
12 matnr TYPE matnr,
13 maktx TYPE maktx,
14 END OF ty_out.
15TYPES tt_out TYPE STANDARD TABLE OF ty_out WITH EMPTY KEY.
16
17DATA(lt_out) = VALUE tt_out(
18 FOR GROUPS grp OF ls IN lt_item
19 GROUP BY ( matnr = ls-matnr )
20 WITHOUT MEMBERS
21 ( matnr = grp-matnr
22 maktx = VALUE #( lt_text[ matnr = grp-matnr ]-maktx OPTIONAL ) ) ).
lt_text[ matnr = ... ]-maktx가 테이블 표현식입니다. 다른 테이블에서 한 행을 찾아 그 필드를 꺼냅니다.
OPTIONAL을 빼면 덤프가 난다
여기가 함정입니다.
!위험맨몸 테이블 표현식lt_text[ matnr = ... ]는 행을 못 찾으면 CX_SY_ITAB_LINE_NOT_FOUND 예외가 납니다. 표현식 안이라 TRY로 감싸기도 번거롭습니다.
✓안전OPTIONAL 또는 DEFAULTVALUE #( ... OPTIONAL )은 못 찾으면 초기값을 돌려줍니다. 대체값을 주고 싶으면 DEFAULT를 씁니다.
1
2maktx = VALUE #( lt_text[ matnr = grp-matnr ]-maktx OPTIONAL )
3
4
5maktx = VALUE #( lt_text[ matnr = grp-matnr ]-maktx DEFAULT '(미등록)' )
조회 테이블은 키를 갖춰라
테이블 표현식 조회는 그룹마다 한 번씩 실행됩니다. 조회 대상이 STANDARD TABLE이고 키가 없으면 매번 처음부터 훑으므로, 데이터가 커질수록 급격히 느려집니다.
SORTED TABLE ... WITH UNIQUE KEY 또는 HASHED TABLE로 선언하거나
- 기존 테이블이라면 보조 키를 추가하세요
이 비용 구조는 SQL 튜닝 ④편에서 다룬 루프 안 조회 문제와 같은 성격입니다.
언제 표현식이고 언제 LOOP인가
✓표현식이 좋을 때- 입력 테이블 → 출력 테이블 변환 한 덩어리
- 그룹별 합계처럼 형태가 정해진 집계
- 중간 변수 없이 바로 넘길 때
!LOOP이 좋을 때- 분기가 많고 예외 처리가 섞일 때
- 중간에 로그를 남기거나 호출이 끼어들 때
- 디버거로 한 줄씩 따라가야 할 때
INFO
표현식은 디버깅이 불편합니다. 한 줄 안에서 모든 일이 일어나기 때문에 중간 상태를 들여다보기 어렵습니다. 로직이 복잡해진다 싶으면 주저 없이 LOOP AT ... GROUP BY로 푸는 편이 낫습니다. 같은 그룹핑을 문장 형태로 쓸 수 있고, 그쪽에서는 GROUP SIZE·GROUP INDEX같은 특수 성분도 활용할 수 있습니다.
자주 겪는 함정
| 증상 | 원인 |
|---|
GROUPS OF와 IN GROUP이 헷갈린다 | GROUPS OF는 그룹을 만드는 쪽, IN GROUP은 만들어진 그룹 안을 도는 쪽입니다 |
WITHOUT MEMBERS를 붙였는데 FOR ... IN GROUP이 문법 오류 | 정상입니다. 멤버가 필요하면 그 추가를 빼세요 |
| 그룹변수를 썼는데 작업영역이 비어 있다 | 그룹키 바인딩에서는 작업영역을 쓸 수 없습니다 |
| 테이블 표현식에서 덤프 | OPTIONAL / DEFAULT 누락 |
| 결과 타입을 못 정한다는 오류 | VALUE #( )의 #는 문맥에서 타입을 추론합니다. 추론이 안 되면 타입명을 직접 적으세요 |
| 건수가 늘자 갑자기 느려짐 | 조회 테이블에 키가 없어 매번 전체 탐색 |
정리
FOR는 루프를 표현식으로 바꿉니다. WHERE, LET, 숫자 반복까지 됩니다.
- 그룹 구문은 둘입니다.
FOR GROUPS ... OF ... IN itab 이 그룹을 만들고, FOR ... IN GROUP grp 가 그 안을 돕니다.
- 그룹을 만들 때 그룹변수를 쓰면 키가, 안 쓰면 대표행이 들어옵니다.
WITHOUT MEMBERS 는 그룹변수가 있을 때만 쓸 수 있고, 붙이면 FOR ... IN GROUP을 쓸 수 없습니다.
- 그룹별 집계는 두 구문을 짝으로 써서
REDUCE로 누적합니다.
- 다른 테이블 값은 테이블 표현식으로 붙이되,
OPTIONAL을 빼먹지 마세요.
- 조회 테이블은 키를 갖춰야 건수가 늘어도 버팁니다.
INFO
사용 가능한 구문과 추가 옵션은 ABAP 릴리스에 따라 다를 수 있습니다. 실제 시스템에서 확인하세요.
Disclaimer — 이 포스트는 AI(Claude)를 활용하여 작성된 초안을 바탕으로 검수 및 보완하여 작성되었습니다. 내용 중 오류나 오타가 있다면 댓글로 알려주시면 감사하겠습니다.