ABAP 에서 두 데이터 집합을 결합할 때 자주 쓰이는 두 가지 패턴이 JOIN 과 FOR ALL ENTRIES(FAE) 입니다. 이름은 비슷하지만 결합 대상이 다릅니다 — JOIN 은 DB 테이블끼리, FAE 는 내부 테이블(itab) 의 키로 DB 테이블 을 조회. 둘 다 ABAP 실무에서 매일 쓰는 핵심 도구지만, 어느 상황에 어느 걸 써야 안전하고 빠른지가 종종 혼동됩니다.
핵심 차이: JOIN 은 한 번의 SELECT 로 DB 측에서 결합을 처리합니다.
FAE 는 ABAP 메모리에 이미 들어있는 itab 의 키를 가지고 다음 테이블을 조회하는 2단계 구조 — 내부적으로는 DB 측에서 IN (...) 또는 UNION 으로 풀려 실행됩니다.
둘은 대체재가 아니라 상호 보완 관계 — JOIN 으로 DB 측 결합을 먼저 하고, 그 결과 itab 을 가지고 FAE 로 추가 테이블을 가져오는 식으로 같이 씁니다.
이 글은 JOIN 의 INNER/LEFT 차이 + FAE 의 동작 메커니즘 + 중복 제거 함정 + 빈 itab 함정 + JOIN 과 FAE 를 같이 쓰는 표준 패턴 까지 한 번에 정리한 메모입니다.
핵심 원리
JOIN 과 FAE 의 본질적 차이.
| 항목 | JOIN | FOR ALL ENTRIES (FAE) |
|---|---|---|
| 결합 대상 | DB 테이블 ↔ DB 테이블 | 내부 테이블(itab) ↔ DB 테이블 |
| 처리 위치 | DB 서버 (한 번의 SELECT) | DB 서버에서 IN/UNION 으로 분할 실행 |
| 구문 | SELECT ... FROM A JOIN B ON A~f = B~f |
SELECT ... FROM B FOR ALL ENTRIES IN it WHERE f = it-f |
| 중복 제거 | SQL 표준 동작 | 자동 중복 제거됨 — primary key 안 들어가면 행이 사라짐 |
| 빈 입력 처리 | N/A | 빈 itab 이면 WHERE 무시되어 전체 SELECT (★ 위험) |
| 성능 | DB 옵티마이저 최적화 가능 | itab 크기/DB 종류/rsdb/max_blocking_factor 에 좌우 |
핵심 트레이드오프: 결합할 두 데이터가 모두 DB 에 있으면 JOIN, 한쪽이 이미 ABAP 메모리에 있는 itab 이면 FAE. 보통 큰 마스터(MARA·EKPO 등) 에서 조회 후, 그 결과로 다른 마스터/트랜잭션을 가져올 때 FAE 패턴이 자주 쓰입니다.
1단계 — JOIN (DB 테이블끼리)
가장 기본 형태. 같은 DB 안의 두 테이블을 한 번의 SELECT 로 결합.
SELECT *
INTO CORRESPONDING FIELDS OF TABLE gt_data2
FROM ztxx0149
JOIN ztxx0148 ON ztxx0149~spmon = ztxx0148~spmon
AND ztxx0149~zwkplace = ztxx0148~zwkplace
WHERE ztxx0149~spmon EQ p_period
AND ztxx0149~zwkplace EQ p_wkplace
ORDER BY ztxx0149~ztype.
JOIN 종류 정리.
| JOIN 종류 | 동작 |
|---|---|
INNER JOIN (기본) |
양쪽 모두 매칭되는 행만 — JOIN 만 쓰면 자동으로 INNER |
LEFT OUTER JOIN |
왼쪽 모두 + 매칭되는 오른쪽 (없으면 NULL/공백) |
RIGHT OUTER JOIN |
오른쪽 모두 + 매칭되는 왼쪽 (NW 7.5+ 지원) |
CROSS JOIN |
데카르트 곱 — 거의 사용 안 함 |
JOIN 작성 시 주의:
- 별칭(Alias) 사용 권장 — 같은 테이블 self-join 이나 가독성 향상
- 필드 참조는
tab또는fieldaliasfield형식 - ON 절에 동등 비교 외
LIKE·BETWEEN도 가능
2단계 — FOR ALL ENTRIES (itab ↔ DB)
itab 에 이미 있는 키를 가지고 다음 테이블을 조회.
" 1차: 일반 SELECT 로 ztxx0148 조회
SELECT *
INTO CORRESPONDING FIELDS OF TABLE gt_data
FROM ztxx0148
WHERE spmon EQ p_period
AND zwkplace EQ p_wkplace
ORDER BY ztype.
" 2차: gt_data 의 키로 ztxx0149 조회 — FAE
SELECT *
INTO CORRESPONDING FIELDS OF TABLE gt_data2
FROM ztxx0149
FOR ALL ENTRIES IN gt_data
WHERE spmon = gt_data-spmon
AND zwkplace = gt_data-zwkplace.
핵심 포인트:
FOR ALL ENTRIES IN itab절은WHERE절보다 위 (FROM 바로 다음)- WHERE 절에서
itab-field식으로 참조 — itab 의 각 행마다 WHERE 가 평가됨 - 결과는 모든 행의 결과를 UNION 한 것
3단계 — JOIN + FAE 같이 쓰기
실무에서는 둘을 같이 씁니다 — DB 측에서 JOIN 으로 1차 결합 후, 그 결과 itab 으로 다른 마스터 정보를 FAE 로 가져오는 패턴.
" 1) DB 측 JOIN 으로 발주헤더+아이템 결합
SELECT a~ebeln, a~lifnr, b~ebelp, b~matnr, b~menge, b~werks
INTO TABLE @DATA(lt_po)
FROM ekko AS a
JOIN ekpo AS b
ON a~ebeln = b~ebeln
WHERE a~bsart EQ @p_bsart
AND a~bedat IN @s_bedat.
IF lt_po IS INITIAL.
MESSAGE 'No PO found' TYPE 'E'.
ENDIF.
" 2) FAE 로 자재명 가져오기 (MAKT)
SELECT matnr, maktx
INTO TABLE @DATA(lt_matkx)
FROM makt
FOR ALL ENTRIES IN @lt_po
WHERE matnr = @lt_po-matnr
AND spras = @sy-langu.
" 3) FAE 로 거래처명 가져오기 (LFA1)
SELECT lifnr, name1
INTO TABLE @DATA(lt_lfa1)
FROM lfa1
FOR ALL ENTRIES IN @lt_po
WHERE lifnr = @lt_po-lifnr.
이 패턴이 표준 — JOIN 으로 핵심 데이터를 한 번에 결합 → FAE 로 마스터 텍스트/속성을 보충.
4단계 — FAE 의 동작 메커니즘 (필독)
FAE 가 내부적으로 어떻게 풀리는지 알아야 함정을 피할 수 있습니다.
DB 옵션 — IN () vs UNION vs OR
ABAP 런타임은 rsdb/prefer_in_itab_opt·DB 종류·필드 수에 따라 FAE 를 다음 중 하나로 변환:
| 옵션 | 변환 형태 |
|---|---|
| IN clause | WHERE matnr IN ('M1', 'M2', ...) — 가장 빠름 (단일 키) |
| OR concat | WHERE (matnr=M1 AND werks=W1) OR (matnr=M2 ...) |
| UNION | 각 itab 행마다 별도 SELECT 후 UNION (HANA 최적화) |
itab 이 크면 자동으로 블록 단위 분할 실행 (rsdb/max_blocking_factor, 보통 5~50) — itab 5만건이면 1000개 블록 × 50회 SELECT 식.
자동 중복 제거
ABAP 이 FAE 결과를 자동으로 DISTINCT 처리합니다. 그래서 결과 itab 의 모든 키 필드를 SELECT 에 포함 안 시키면 중복으로 보여 행이 사라집니다.
" ★ 위험 — primary key 인 ebelp 가 SELECT 에 없음
SELECT ebeln, matnr, menge " ebelp 누락 → 자재가 같은 행은 1개만 남음
INTO TABLE @DATA(lt_wrong)
FROM ekpo
FOR ALL ENTRIES IN @lt_po
WHERE ebeln = @lt_po-ebeln.
" ✓ 안전 — primary key 모두 포함
SELECT ebeln, ebelp, matnr, menge
INTO TABLE @DATA(lt_safe)
FROM ekpo
FOR ALL ENTRIES IN @lt_po
WHERE ebeln = @lt_po-ebeln.
빈 itab 함정 (★ 가장 위험)
itab 이 비어있는데 FAE 를 호출하면 — WHERE 절 무시 + 전체 테이블 SELECT 가 발생합니다. EKPO 같은 큰 테이블이면 시스템 다운 직행.
" ★ 절대 금지 — lt_po 가 비어있으면 EKBE 전체 SELECT
IF lt_po IS NOT INITIAL. " ★ FAE 전에 반드시 체크
SELECT ...
FROM ekbe
FOR ALL ENTRIES IN @lt_po
WHERE ebeln = @lt_po-ebeln.
ENDIF.
흔히 빠뜨리는 함정
빈 itab + FAE → 전체 SELECT
가장 흔하고 가장 위험한 함정. 반드시 IF itab IS NOT INITIAL 가드 + 빈 결과 처리 분기.
자동 중복 제거 — primary key 누락
결과 itab 의 키 필드를 모두 SELECT 에 넣어야 함. 안 그러면 중복 처리되어 행이 사라짐. ALV 표시할 때 행 수가 적게 보이면 이 함정 의심.
FOR ALL ENTRIES 가 WHERE 안에 들어감
문법 위치 헷갈림. FROM 다음 → FOR ALL ENTRIES IN → WHERE 순서.
itab 정렬·중복 제거 없이 FAE
itab 이 정렬되어 있고 중복 없으면 FAE 가 더 효율적. SORT itab BY key. DELETE ADJACENT DUPLICATES FROM itab COMPARING key. 가 FAE 직전 패턴.
JOIN 으로 충분한데 FAE 2단계 작성
DB 결합 가능한 케이스를 굳이 itab 으로 끌어와 FAE 처리하면 DB 라운드트립 손해. 순수 DB 측 결합 은 JOIN.
LEFT OUTER JOIN 의 ON vs WHERE
LEFT OUTER JOIN 의 오른쪽 테이블 필터를 WHERE 에 두면 INNER JOIN 처럼 동작. 오른쪽 필터는 ON 절 안에.
* SELECT 후 CORRESPONDING FIELDS
신규 코드는 명시적 컬럼 나열 권장. SELECT * + INTO CORRESPONDING 은 네트워크 트래픽 손해 + 코드 가독성 저해.
FAE 와 ORDER BY
FAE 에서 ORDER BY 는 동작하지만, 내부적으로 블록 분할 실행 후 통합 정렬이라 성능 저하. ABAP 측에서 SORT 권장.
너무 큰 itab → 메모리·DB 부담
10만 건 이상의 itab 으로 FAE 돌리면 DB 측 IN 절이 폭주. 사전 필터링 + 청크 분할 검토.
전체 코드 — 복사용 통합본
JOIN + FAE 표준 패턴 통합본입니다. 발주(EKKO/EKPO) 조회 후 마스터(MAKT/LFA1) 보충 패턴.
*&---------------------------------------------------------------------*
*& JOIN + FOR ALL ENTRIES 표준 패턴
*&---------------------------------------------------------------------*
REPORT zexample_join_fae NO STANDARD PAGE HEADING.
TABLES ekko.
SELECT-OPTIONS:
s_ebeln FOR ekko-ebeln,
s_bedat FOR ekko-bedat OBLIGATORY,
s_bsart FOR ekko-bsart.
DATA: BEGIN OF gs_out,
ebeln TYPE ekko-ebeln,
bedat TYPE ekko-bedat,
lifnr TYPE ekko-lifnr,
lifnr_nm TYPE lfa1-name1,
ebelp TYPE ekpo-ebelp,
matnr TYPE ekpo-matnr,
matnr_nm TYPE makt-maktx,
menge TYPE ekpo-menge,
werks TYPE ekpo-werks,
END OF gs_out,
gt_out LIKE TABLE OF gs_out.
START-OF-SELECTION.
* ---------------------------------------------------------
* 1) DB 측 JOIN — EKKO + EKPO 결합
* ---------------------------------------------------------
SELECT a~ebeln, a~bedat, a~lifnr,
b~ebelp, b~matnr, b~menge, b~werks
INTO CORRESPONDING FIELDS OF TABLE @gt_out
FROM ekko AS a
INNER JOIN ekpo AS b
ON a~ebeln = b~ebeln
WHERE a~ebeln IN @s_ebeln
AND a~bedat IN @s_bedat
AND a~bsart IN @s_bsart
AND b~loekz = @space. " 삭제 표시 안 된 라인만
IF gt_out IS INITIAL.
MESSAGE 'No PO found' TYPE 'I'.
RETURN.
ENDIF.
* ---------------------------------------------------------
* 2) FAE — 자재명 (MAKT) 보충
* ★ primary key (matnr) 를 결과 SELECT 에 포함
* ---------------------------------------------------------
IF gt_out IS NOT INITIAL. " ★ 빈 itab 가드 필수
SELECT matnr, maktx
INTO TABLE @DATA(lt_makt)
FROM makt
FOR ALL ENTRIES IN @gt_out
WHERE matnr = @gt_out-matnr
AND spras = @sy-langu.
SORT lt_makt BY matnr.
LOOP AT gt_out ASSIGNING FIELD-SYMBOL(<o>).
READ TABLE lt_makt
INTO DATA(ls_makt)
WITH KEY matnr = <o>-matnr
BINARY SEARCH.
IF sy-subrc = 0.
<o>-matnr_nm = ls_makt-maktx.
ENDIF.
ENDLOOP.
ENDIF.
* ---------------------------------------------------------
* 3) FAE — 거래처명 (LFA1) 보충
* ---------------------------------------------------------
IF gt_out IS NOT INITIAL.
SELECT lifnr, name1
INTO TABLE @DATA(lt_lfa1)
FROM lfa1
FOR ALL ENTRIES IN @gt_out
WHERE lifnr = @gt_out-lifnr.
SORT lt_lfa1 BY lifnr.
LOOP AT gt_out ASSIGNING FIELD-SYMBOL(<o2>).
READ TABLE lt_lfa1
INTO DATA(ls_lfa1)
WITH KEY lifnr = <o2>-lifnr
BINARY SEARCH.
IF sy-subrc = 0.
<o2>-lifnr_nm = ls_lfa1-name1.
ENDIF.
ENDLOOP.
ENDIF.
* ---------------------------------------------------------
* 4) 결과 출력
* ---------------------------------------------------------
SORT gt_out BY ebeln ebelp.
cl_demo_output=>display( gt_out ).
* ※ 참고:
* - JOIN 은 DB 측 결합 — INNER/LEFT OUTER 구분
* - FAE 직전 IF IS NOT INITIAL 가드 필수 (빈 itab → 전체 SELECT)
* - FAE 결과는 자동 DISTINCT — primary key 모두 SELECT 에 포함
* - 큰 itab 은 SORT + DELETE ADJACENT DUPLICATES 로 사전 정리
* - 대안: 7.40+ INNER JOIN + LEFT JOIN 한 번에 끝낼 수도 있음
요약
| 단계 | 처리 | 핵심 |
|---|---|---|
| 1 | JOIN — DB ↔ DB | SELECT ... FROM A JOIN B ON A~f = B~f WHERE ... — INNER/LEFT OUTER |
| 2 | FAE — itab ↔ DB | SELECT ... FROM B FOR ALL ENTRIES IN it WHERE f = it-f |
| 3 | 빈 itab 가드 | IF it IS NOT INITIAL 없이 FAE → 전체 SELECT 사고 |
| 4 | 중복 제거 | FAE 결과 자동 DISTINCT — primary key 모두 SELECT 에 포함 필수 |
| 5 | 같이 쓰기 | JOIN 으로 핵심 결합 → FAE 로 마스터 텍스트(자재명·거래처명) 보충 |
JOIN 은 DB 안의 두 테이블 을 한 번의 SELECT 로 결합하고, FOR ALL ENTRIES 는 이미 ABAP 에 있는 itab 을 가지고 다음 DB 테이블을 조회합니다.
둘은 대체재가 아니라 상호 보완 — 실무에서는 JOIN 으로 핵심 트랜잭션 데이터(EKKO+EKPO)를 결합하고 FAE 로 마스터 텍스트(MAKT·LFA1)를 보충하는 패턴이 표준입니다.
가장 큰 함정은 빈 itab + FAE = 전체 SELECT 와 결과 자동 DISTINCT 로 인한 행 누락 — IF itab IS NOT INITIAL 가드와 primary key 의 SELECT 포함, 이 두 가지만 챙기면 안전합니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
JOIN·INNER JOIN·LEFT OUTER JOIN·FOR ALL ENTRIES 는 ABAP Open SQL 표준 문법으로 시스템 버전 의존 없이 동작합니다.
RIGHT OUTER JOIN 은 NetWeaver 7.5 이상, @ 인라인 변수 / INTO TABLE @DATA(...) 는 7.40+ 에서 사용 가능합니다. FAE 의 내부 변환 방식(IN/OR/UNION) 과 블록 크기는 DB 종류(HANA/Oracle/DB2) 및 프로파일 파라미터에 따라 다르므로, 운영 적용 전 대용량 케이스에서 ST05 SQL Trace 로 실행 계획을 검증하시기 바랍니다.