1. MESHES란 무엇인가 — 개념과 등장 배경
ABAP에서 여러 내부 테이블 사이의 관계를 다룰 때, 전통적으로는 LOOP 안에 READ TABLE을 중첩하거나, 임시 구조체를 만들어 수동으로 매핑하는 방식이 일반적이었습니다. 이러한 패턴은 코드 가독성이 떨어지고, 키 변경 시 여러 곳을 동시에 수정해야 하는 유지보수 부담을 만듭니다. MESH(메쉬, 복수형 MESHES)는 ABAP 7.40 SP08에서 도입되어 7.50 이후 본격적으로 활용되기 시작한 데이터 구조 타입으로, 여러 내부 테이블을 하나의 컨테이너로 묶고 각 테이블 간의 연관 관계(Association)를 타입 레벨에서 선언할 수 있게 해줍니다.
SQL JOIN이 데이터베이스 레벨에서 테이블을 결합한다면, MESHES는 ABAP 메모리 위에 올라온 내부 테이블끼리 동일한 의미의 결합을 표현합니다. 즉, OPEN SQL을 추가로 실행하지 않고도 이미 메모리에 적재된 테이블 사이의 부모-자식, 마스터-디테일 관계를 객체 그래프처럼 탐색할 수 있습니다. FOR ... IN GROUP OF나 REDUCE로도 비슷한 집계는 가능하지만, 관계 자체를 타입으로 굳혀 두는 것은 MESHES만의 강점입니다.
이 글에서는 ABAP 7.50 이상(권장: NW AS ABAP 7.54 / S/4HANA 2021 이상)을 전제로, 두 개 이상의 내부 테이블을 어떻게 MESH 한 덩어리로 연결하고 실전에서 활용하는지 단계별로 살펴봅니다. 다음과 같은 항목을 다룰 예정입니다.
- MESH 타입 선언 및 ASSOCIATION 정의 방법
- 마스터-디테일 시나리오에서의 LOOP 활용
- 중첩 MESH로 3계층 데이터 모델 처리
- READ TABLE 대비 성능 특성과 주의점
2. 내부 테이블 간 관계 모델링 — 연관 키 설계
MESH를 설계하기 전에 가장 먼저 해야 할 일은 관계의 카디널리티를 명확히 정하는 것입니다. 1:1인지, 1:N인지, N:M인지에 따라 association의 cardinality 표기가 달라지고, 잘못 선언하면 런타임에서 의도하지 않은 예외나 빈 결과를 받게 됩니다.
실무에서 자주 보이는 시나리오는 다음과 같습니다.
- 수주 헤더(VBAK) ↔ 수주 아이템(VBAP): 1:N 관계, 키는 VBELN
- 고객(KNA1) ↔ 수주 헤더: 1:N 관계, 키는 KUNNR
- 자재(MARA) ↔ 수주 아이템: 1:N 관계, 키는 MATNR
MESH는 이러한 관계를 ASSOCIATION ... TO ... CARDINALITY n..m USING KEY ... WHERE ... 구문으로 표현합니다. 여기서 USING KEY 절에 명시되는 키는 대상 테이블에 반드시 정의되어 있어야 하는 secondary key입니다. 이 부분이 MESH의 가장 큰 진입 장벽이지만, 동시에 성능을 보장하는 핵심 메커니즘이기도 합니다. 키가 정의되지 않은 채 association을 선언하면 컴파일러가 단호히 거부합니다.
3. MESH 기본 문법 — 선언부터 실행까지
MESH 타입은 일반적인 STRUCTURE 안에 여러 internal table 컴포넌트를 두고, 각 컴포넌트 사이에 ASSOCIATION을 거는 형태로 선언됩니다. 가장 단순한 두 테이블 연결의 골격은 다음과 같습니다.
TYPES:
BEGIN OF ty_order,
order_id TYPE vbeln_va,
customer TYPE kunnr,
net_amount TYPE netwr,
END OF ty_order,
BEGIN OF ty_item,
order_id TYPE vbeln_va,
posnr TYPE posnr_va,
material TYPE matnr,
quantity TYPE kwmeng,
END OF ty_item.
TYPES:
tt_order TYPE SORTED TABLE OF ty_order
WITH UNIQUE KEY order_id,
tt_item TYPE SORTED TABLE OF ty_item
WITH UNIQUE KEY order_id posnr
WITH NON-UNIQUE SORTED KEY by_order
COMPONENTS order_id.
TYPES:
BEGIN OF MESH mt_sales,
orders TYPE tt_order
ASSOCIATION items TO items
ON order_id = order_id,
items TYPE tt_item,
END OF MESH mt_sales.
여기서 핵심은 세 가지입니다. 첫째, MESH는 BEGIN OF MESH ... END OF MESH로 감싸야 하며 일반 구조체가 아닙니다. 둘째, ASSOCIATION의 ON 절은 SQL JOIN의 ON과 동일한 의미로 좌측은 출발 테이블 컬럼, 우측은 도착 테이블 컬럼입니다. 셋째, 도착 테이블에는 association을 빠르게 풀어내기 위한 secondary key(여기서는 by_order)가 정의되어 있어야 실전 성능이 보장됩니다.
실제 데이터 변수는 다음처럼 선언합니다.
DATA gs_sales TYPE mt_sales.
" 데이터 채우기
gs_sales-orders = VALUE #(
( order_id = '0000010001' customer = '0000100001' net_amount = '5000.00' )
( order_id = '0000010002' customer = '0000100002' net_amount = '8200.00' )
).
gs_sales-items = VALUE #(
( order_id = '0000010001' posnr = '000010' material = 'MAT-A' quantity = 5 )
( order_id = '0000010001' posnr = '000020' material = 'MAT-B' quantity = 3 )
( order_id = '0000010002' posnr = '000010' material = 'MAT-C' quantity = 1 )
).
4. 실전 예제: 수주와 수주 항목 연결
이제 MESH의 진가가 발휘되는 순간입니다. 헤더를 돌면서 해당 헤더에 속한 아이템만 골라 처리하고 싶을 때, 일반적인 ABAP이라면 다음과 같이 작성합니다.
LOOP AT gs_sales-orders INTO DATA(ls_order).
LOOP AT gs_sales-items INTO DATA(ls_item)
USING KEY by_order
WHERE order_id = ls_order-order_id.
" 처리...
ENDLOOP.
ENDLOOP.
틀린 코드는 아니지만, association을 모르는 사람에게는 "어떤 키로 어떻게 연결되는지"가 코드 곳곳에 흩어집니다. MESH를 쓰면 동일한 로직을 다음처럼 압축할 수 있습니다.
LOOP AT gs_sales-orders ASSIGNING FIELD-SYMBOL(<fs_order>).
WRITE: / 'Order:', <fs_order>-order_id,
'Customer:', <fs_order>-customer.
LOOP AT gs_sales-orders\items[ <fs_order> ]
ASSIGNING FIELD-SYMBOL(<fs_item>).
WRITE: / ' Item:', <fs_item>-posnr,
'Mat:', <fs_item>-material,
'Qty:', <fs_item>-quantity.
ENDLOOP.
ENDLOOP.
gs_sales-orders\items[ <fs_order> ] 표기가 처음에는 낯설어 보이지만, 의미는 직관적입니다. 백슬래시(\)는 "association을 따라가라"는 연산자이고, 대괄호 안의 <fs_order>는 출발점이 되는 행입니다. 즉, "현재 order 행에서 items association을 따라간 부분 결과"가 곧 LOOP 대상이 됩니다.
특정 헤더 하나에 대해 아이템 합계를 구해야 한다면 다음처럼 표현식 하나로 끝낼 수 있습니다.
DATA(lv_total_qty) = REDUCE kwmeng(
INIT s = CONV kwmeng( 0 )
FOR <fs_item> IN gs_sales-orders\items[ KEY order_id
order_id = '0000010001' ]
NEXT s = s + <fs_item>-quantity ).
또한 단일 행을 가져올 때는 다음과 같이 인라인 선언과 결합할 수 있습니다.
DATA(ls_first_item) =
gs_sales-orders\items[ KEY order_id
order_id = '0000010002' ][ 1 ].
5. LOOP AT ... USING KEY와의 조합 패턴
MESH의 association은 내부적으로 도착 테이블의 secondary key를 사용합니다. 따라서 LOOP 안에서 association을 따라갈 때 명시적으로 USING KEY를 적어 키를 강제할 수 있습니다. 잘 설계된 sorted 또는 hashed secondary key를 함께 두면 1:N 탐색이 O(log N) 또는 O(1)에 수렴합니다.
LOOP AT gs_sales-orders\items[ <fs_order> ]
USING KEY order_id
ASSIGNING FIELD-SYMBOL(<fs_item>)
WHERE quantity > 2.
" 수량이 2 초과인 아이템만 후처리
<fs_item>-quantity = <fs_item>-quantity * '1.10'.
ENDLOOP.
주의할 점은, association을 따라간 결과 집합도 일반 internal table처럼 동작하지만 읽기 전용 뷰가 아니라 원본 테이블에 대한 참조라는 사실입니다. 따라서 위 코드에서 <fs_item>-quantity를 수정하면 gs_sales-items의 원본 행이 직접 갱신됩니다.
6. 성능 고려사항 — 대량 데이터에서의 MESH vs READ TABLE
MESH의 성능은 거의 전적으로 도착 테이블에 정의된 secondary key 품질에 달려 있습니다. secondary key 없이 association을 따라가면 내부적으로 standard key scan과 다를 바 없고, 오히려 association 해석 오버헤드가 더해질 수 있습니다.
실무에서 100만 행 단위의 수주 아이템을 다루는 배치 프로그램이라면 다음 원칙을 권장합니다.
- 도착 테이블에 NON-UNIQUE SORTED KEY를 association 조인 컬럼으로 정의한다.
- 읽기만 한다면
ASSIGNING FIELD-SYMBOL을 사용해 복사를 피한다. - 특정 행 하나만 필요하다면
READ TABLE ... WITH TABLE KEY가 association보다 의도가 명확하고 약간 빠를 수 있다. - 여러 행을 일관된 패턴으로 반복 조회해야 한다면 association이 코드 가독성과 성능 양쪽에서 일반적으로 유리하다.
벤치마크할 때는 SAT(Runtime Analysis) 또는 SE30으로 association 라인을 별도로 측정하는 것이 좋습니다. 동일 키로 READ TABLE을 수십만 번 반복하는 패턴이 있다면 MESH로 변경했을 때 평균 30~60%의 시간 단축이 보고된 사례가 있지만, 키 설계가 잘못되면 오히려 더 느려질 수도 있다는 점을 유념해야 합니다.
7. 중첩 MESH — 3개 테이블 연결 패턴
실무 데이터 모델은 두 테이블로 끝나지 않습니다. 고객 → 수주 헤더 → 수주 아이템처럼 3계층 이상이 일반적이며, MESH는 이를 자연스럽게 표현합니다.
TYPES:
BEGIN OF ty_customer,
customer TYPE kunnr,
name TYPE name1_gp,
END OF ty_customer,
tt_customer TYPE SORTED TABLE OF ty_customer
WITH UNIQUE KEY customer,
tt_order_v2 TYPE SORTED TABLE OF ty_order
WITH UNIQUE KEY order_id
WITH NON-UNIQUE SORTED KEY by_customer
COMPONENTS customer.
TYPES:
BEGIN OF MESH mt_crm,
customers TYPE tt_customer
ASSOCIATION orders TO orders
ON customer = customer,
orders TYPE tt_order_v2
ASSOCIATION items TO items
ON order_id = order_id,
items TYPE tt_item,
END OF MESH mt_crm.
이제 한 고객의 모든 아이템을 한 번에 가져오려면 association을 연속으로 따라갑니다.
DATA gs_crm TYPE mt_crm.
LOOP AT gs_crm-customers ASSIGNING FIELD-SYMBOL(<fs_cust>).
LOOP AT gs_crm-customers\orders[ <fs_cust> ]
ASSIGNING FIELD-SYMBOL(<fs_ord>).
LOOP AT gs_crm-orders\items[ <fs_ord> ]
ASSIGNING FIELD-SYMBOL(<fs_it>).
WRITE: / <fs_cust>-name,
<fs_ord>-order_id,
<fs_it>-material,
<fs_it>-quantity.
ENDLOOP.
ENDLOOP.
ENDLOOP.
8. 주요 실수와 디버깅 팁
- secondary key 누락으로 인한 컴파일 오류: ASSOCIATION 정의에서 참조하는 키가 도착 테이블에 없으면 컴파일이 막힙니다. 키 이름의 오타, 컴포넌트 순서 차이가 가장 흔한 원인입니다.
- CX_SY_ITAB_LINE_NOT_FOUND 예외:
orders\items[ ... ]가 단일 행이 아니라 부분 결과 집합(테이블)인데, 마치 단일 행처럼 인덱스 없이 직접 접근하면 예외가 발생합니다. 한 행을 원하면 명시적으로[ 1 ]인덱스를 붙이거나LINE_EXISTS( )로 먼저 검사하세요. - field-symbol 갱신이 원본 변경으로 이어짐: association 결과는 원본의 참조입니다. 의도하지 않은 수정 사이드 이펙트를 막으려면 읽기 전용 구간에서는
INTO+ DATA 변수로 복사해 사용하세요. - 비어 있는 association 결과: 데이터는 분명히 있는데 결과가 0건이라면 ON 절의 컬럼 데이터 타입과 길이가 양쪽에서 다를 가능성이 큽니다. 특히 CHAR(10) vs CHAR(20)처럼 폭이 다르면 매칭이 실패합니다.
- ADT 디버거 활용: SAP GUI 디버거에서 MESH는 일반 STRUCTURE처럼 펼쳐지지만, association은 동적으로 계산되므로 변수 트리에 즉시 나타나지 않습니다. ABAP Development Tools(ADT) 디버거의 평가 표현식 창에서
gs_sales-orders\items[ ... ]를 입력하면 실시간으로 결과 테이블을 확인할 수 있습니다. - 유닛 테스트: ABAP Unit으로 association 결과를 검증할 때, 행 개수와 정렬 순서가 도착 테이블의 secondary key 구성에 따라 달라질 수 있다는 점을 명시적으로 반영하세요.
MESH는 OPEN SQL JOIN의 ABAP 메모리 버전으로 이해하면 가장 빠르게 익숙해집니다. SELECT로 데이터를 가져온 직후 ALV 출력, BAdI 입력 검증, 시뮬레이션 로직 등 "이미 메모리에 올라온 데이터끼리의 관계 탐색"이 필요한 모든 지점에서 기존의 중첩 LOOP를 대체할 수 있는 강력한 도구입니다.
댓글 0
아직 댓글이 없습니다.