본문 바로가기
ABAP 문법 & 기법

[SAP ABAP] JOIN vs FOR ALL ENTRIES — 테이블 vs Internal Table 조인 / 차이 / 함정

by Song.sh 2026. 5. 15.

ABAP 에서 두 데이터 집합을 결합할 때 자주 쓰이는 두 가지 패턴이 JOINFOR 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 이나 가독성 향상
  • 필드 참조는 tabfield 또는 aliasfield 형식
  • 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 ENTRIESWHERE 안에 들어감

문법 위치 헷갈림. FROM 다음 → FOR ALL ENTRIES INWHERE 순서.

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 로 실행 계획을 검증하시기 바랍니다.