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

[SAP ABAP] 다중 매개변수 동적 조건 — UNION · 동적 WHERE · RANGE IN — 입력값 N개 처리

by Song.sh 2026. 5. 15.

SAP 개발하다 보면 자주 만나는 상황 — 함수의 입력값이 여러 개인데 사용자가 어떤 건 채우고 어떤 건 비워서 넘기는 경우. 예를 들어 입력 매개변수가 4개(i_keyno·i_ebeln·i_ebelp·i_matnr) 일 때, 채워진 것만 조건에 넣어 SELECT 를 돌려야 합니다.

 

단순히 WHERE field = @i_value 만 쓰면 비어 있는 매개변수는 빈 문자열로 비교되어 매칭이 안 됩니다. IF 문으로 4가지 경우를 다 분기하면 코드가 16가지로 폭주. 해결 방법은 세 가지 — UNION 으로 SELECT 합치기 / 동적 WHERE 문자열 조립 / RANGE 변수 + IN 절.

 

이 글은 3가지 방법의 코드 형태·장단점·실무 추천 패턴(RANGE) + 자주 빠뜨리는 함정 까지 한 번에 정리한 메모입니다.


핵심 원리

3가지 방법의 작동 방식 비교.

방법 로직 언제 쓰나
UNION 각 조건별 SELECT 를 합쳐서 결과 합집합(OR 로직) "이 조건 또는 저 조건" — 합집합 필요
동적 WHERE WHERE 절을 문자열로 조립 → 실행 시점에 적용 조건 갯수·형태가 매우 다양할 때
RANGE + IN (★ 권장) 매개변수별 RANGE 테이블 → WHERE f IN range 대부분의 경우 — 가장 안전하고 깔끔

핵심 트레이드오프: 합집합(OR) 이 필요하면 UNION, 정말 다양한 조건(LIKE·BETWEEN 섞임) 이면 동적 WHERE, AND 로 채워진 것만 적용 이라는 가장 일반적인 케이스는 RANGE + IN. 보통 90% 이상은 RANGE 패턴 하나로 처리됩니다.


1단계 — UNION / UNION ALL 방법

각 매개변수마다 SELECT 를 따로 작성한 뒤 UNION 으로 합칩니다.

SELECT keyno, ebeln, ebelp, matnr,
       qty_a, qty_b, qty_c, qty_d,
       qty_total
  FROM ztxx0123
  WHERE keyno = @i_keyno

  UNION

  SELECT keyno, ebeln, ebelp, matnr,
         qty_a, qty_b, qty_c, qty_d,
         qty_total
    FROM ztxx0123
    WHERE ebeln = @i_ebeln

  UNION

  SELECT keyno, ebeln, ebelp, matnr,
         qty_a, qty_b, qty_c, qty_d,
         qty_total
    FROM ztxx0123
    WHERE ebelp = @i_ebelp

  UNION

  SELECT keyno, ebeln, ebelp, matnr,
         qty_a, qty_b, qty_c, qty_d,
         qty_total
    FROM ztxx0123
    WHERE matnr = @i_matnr
  INTO CORRESPONDING FIELDS OF TABLE @et_data.

핵심 포인트와 제약:

  • OR 로직 — 4개 매개변수 중 어느 하나라도 매칭되면 결과에 포함
  • 모든 SELECT 의 컬럼 순서·갯수가 동일해야 함 — SAP UNION 규칙
  • NEW Open SQL(7.40+) 에서만 사용 가능 — 인라인 변수 @i_xxx 형식
  • UNION ALL 은 중복 행도 살리고, 그냥 UNION 은 중복 제거 (성능 손해)

언제 쓰면 좋은가:

  • "발주번호 OR 자재번호 OR 키 번호 중 아무거나 매칭되면 조회" — 사용자에게 "찾기" 기능 제공할 때
  • AND 로직 필요하면 부적합

2단계 — 동적 WHERE 절 (문자열 조립)

매개변수 채워진 것만 골라 WHERE 절을 문자열로 만들고 SELECT 에 전달.

DATA: lv_where TYPE string.

IF i_ebeln IS NOT INITIAL.
  IF lv_where IS NOT INITIAL.
    lv_where = lv_where && ' AND ebeln = @i_ebeln'.
  ELSE.
    lv_where = lv_where && 'ebeln = @i_ebeln'.
  ENDIF.
ENDIF.

IF i_ebelp IS NOT INITIAL.
  IF lv_where IS NOT INITIAL.
    lv_where = lv_where && ' AND ebelp = @i_ebelp'.
  ELSE.
    lv_where = lv_where && 'ebelp = @i_ebelp'.
  ENDIF.
ENDIF.

IF i_keyno IS NOT INITIAL.
  IF lv_where IS NOT INITIAL.
    lv_where = lv_where && ' AND keyno = @i_keyno'.
  ELSE.
    lv_where = lv_where && 'keyno = @i_keyno'.
  ENDIF.
ENDIF.

IF i_matnr IS NOT INITIAL.
  IF lv_where IS NOT INITIAL.
    lv_where = lv_where && ' AND matnr = @i_matnr'.
  ELSE.
    lv_where = lv_where && 'matnr = @i_matnr'.
  ENDIF.
ENDIF.

SELECT *
  INTO CORRESPONDING FIELDS OF TABLE et_data
  FROM ztxx0123
  WHERE (lv_where).

핵심 포인트와 제약:

  • AND 로직 — 채워진 매개변수 모두 만족해야 결과
  • WHERE 절을 괄호로 감싸서 전달WHERE (lv_where)
  • 비교 연산자 자유롭게(LIKE·BETWEEN·<·> 등) 조립 가능 — 가장 유연
  • 단점: 문자열 오타나면 런타임 에러, 디버깅 어려움, 코드 길어짐, SQL 인젝션 위험(사용자 입력 직접 받는 경우)

3단계 — RANGE 변수 + WHERE IN (★ 권장)

매개변수마다 RANGE 테이블을 만들고 IS NOT INITIAL 일 때만 채워서 IN 절로 SELECT.

" 1) RANGE 테이블 선언
DATA: r_keyno TYPE RANGE OF ekko-bedat,
      r_ebeln TYPE RANGE OF ekko-ebeln,
      r_ebelp TYPE RANGE OF ekpo-ebelp,
      r_matnr TYPE RANGE OF mara-matnr.

" 2) IS NOT INITIAL 인 매개변수만 RANGE 에 추가
IF i_keyno IS NOT INITIAL.
  APPEND VALUE #( sign = 'I' option = 'EQ' low = i_keyno ) TO r_keyno.
ENDIF.
IF i_ebeln IS NOT INITIAL.
  APPEND VALUE #( sign = 'I' option = 'EQ' low = i_ebeln ) TO r_ebeln.
ENDIF.
IF i_ebelp IS NOT INITIAL.
  APPEND VALUE #( sign = 'I' option = 'EQ' low = i_ebelp ) TO r_ebelp.
ENDIF.
IF i_matnr IS NOT INITIAL.
  APPEND VALUE #( sign = 'I' option = 'EQ' low = i_matnr ) TO r_matnr.
ENDIF.

" 3) SELECT — 비어있는 RANGE 는 자동으로 WHERE 무시
SELECT *
  INTO CORRESPONDING FIELDS OF TABLE et_data
  FROM ztxx0123
  WHERE keyno IN r_keyno
    AND ebeln IN r_ebeln
    AND ebelp IN r_ebelp
    AND matnr IN r_matnr.

핵심 포인트:

  • AND 로직 — 채워진 RANGE 끼리는 AND 로 적용 (비어있는 RANGE 는 모든 행 통과)
  • RANGE 테이블 구조 = sign(I/E) · option(EQ/BT/CP 등) · low · high 4 필드 (SELECT-OPTIONS 와 동일)
  • 빈 RANGE 의 IN = 모든 행 통과 — IF 분기 없어도 동작 ★
  • 한 매개변수에 여러 값 받기 쉬움 (APPEND 여러 번)
  • SQL 인젝션 위험 없음, 정적 SQL 그대로

가장 큰 장점은 코드가 깔끔 하고 확장이 쉽다 는 점 — 매개변수가 10개가 되어도 같은 패턴 반복이라 유지보수 편합니다.


4단계 — 비교와 선택 기준

세 방법 한눈에 정리.

항목 UNION 동적 WHERE RANGE + IN
로직 OR (합집합) AND (조립 자유) AND
코드 길이 길다 (SELECT 반복) 매우 길다 (IF 분기) 짧다
오타 위험 낮음 (컴파일 체크) 높음 (문자열, 런타임 에러) 낮음
SQL 인젝션 없음 위험 (사용자 입력 시) 없음
확장성 매개변수 늘면 SELECT 추가 IF 폭주 같은 패턴 반복
비교 연산자 SELECT 별로 다양 가장 자유 EQ/BT/CP/NE 등 RANGE 옵션

실무 선택 가이드:

  • 단순 "AND 로 채워진 것만 적용" → RANGE + IN (90%)
  • 복잡한 OR / 다른 테이블 조합 결과 → UNION
  • WHERE 자체가 동적으로 달라져야 함 (LIKE·BETWEEN 자유 조립) → 동적 WHERE

흔히 빠뜨리는 함정

IF 분기 안 하고 WHERE field = @i_value 만 쓰기

비어있는 매개변수면 빈 문자열로 비교되어 매칭이 0건. 반드시 IS NOT INITIAL 가드 또는 RANGE 패턴 사용.

UNION 의 컬럼 갯수·순서 다름

각 SELECT 의 컬럼 갯수와 데이터 타입이 똑같지 않으면 컴파일 에러. 컬럼 변경 시 모든 SELECT 동시 수정.

동적 WHERE 의 첫 조건 AND 빠뜨림

문자열 조립 시 첫 조건에는 AND 가 안 붙어야 함. IF lv_where IS NOT INITIAL 가드로 분기.

동적 WHERE 인라인 변수 누락

문자열 안에서 변수 참조는 @i_ebeln 식으로 인라인 변수 표기. 그냥 i_ebeln 만 쓰면 인식 안 됨.

RANGE 의 sign·option 오타

sign'I'/'E', option'EQ'/'BT'/'CP'/'NE' 등 정확한 두 글자. 소문자나 다른 표기 X.

RANGE 의 빈 IN 동작 이해 부족

빈 RANGE 에 IN 절은 모든 행 통과 가 핵심 — 이 동작 알면 IS NOT INITIAL 가드 없이 그냥 IN 만 써도 OK.

UNION 대신 그냥 OR 로 작성

WHERE keyno = @i_keyno OR ebeln = @i_ebeln OR ...

이런 코드는 비어있는 매개변수도 OR 에 포함 되어 의도와 다른 결과. 빈 매개변수 = 빈 문자열 매칭이 추가됨. UNION 또는 RANGE 가 안전.

CORRESPONDING FIELDS 누락

UNION 또는 동적 WHERE 결과를 일반 itab 으로 받을 때 컬럼이 정확히 안 맞으면 빈 값. INTO CORRESPONDING FIELDS OF TABLE 또는 명시적 INTO 컬럼 매핑 필요.

동적 WHERE 의 SQL 인젝션 위험

사용자 입력을 그대로 lv_where 에 붙이면 위험. 입력값은 인라인 변수(@i_xxx) 로만 전달하고, 필드명·연산자만 문자열 조립.


전체 코드 — 복사용 통합본

세 방법을 한 펑션 모듈에 모두 담은 통합본입니다. SE37 에 새 펑션 만들어 그대로 복사하면 동작합니다(테이블·매개변수는 환경에 맞게 교체).

*&---------------------------------------------------------------------*
*& 다중 입력 매개변수 — UNION / 동적 WHERE / RANGE+IN 세 패턴
*&---------------------------------------------------------------------*
FUNCTION z_example_dynamic_select.
*"----------------------------------------------------------------------
*"  IMPORTING
*"     VALUE(I_MODE)   TYPE  CHAR1   DEFAULT '3'    " 1=UNION 2=DYN_WHERE 3=RANGE
*"     VALUE(I_KEYNO)  TYPE  ekko-bedat OPTIONAL
*"     VALUE(I_EBELN)  TYPE  ekko-ebeln OPTIONAL
*"     VALUE(I_EBELP)  TYPE  ekpo-ebelp OPTIONAL
*"     VALUE(I_MATNR)  TYPE  mara-matnr OPTIONAL
*"  EXPORTING
*"     VALUE(ET_DATA)  TYPE  ztt_example_data
*"----------------------------------------------------------------------

  CASE i_mode.

* ===============================================================
* 1) UNION — OR 로직, 매개변수 어느 하나라도 매칭
* ===============================================================
    WHEN '1'.
      SELECT keyno, ebeln, ebelp, matnr, qty_a, qty_b, qty_total
        FROM ztxx0123
        WHERE keyno = @i_keyno

        UNION

        SELECT keyno, ebeln, ebelp, matnr, qty_a, qty_b, qty_total
          FROM ztxx0123
          WHERE ebeln = @i_ebeln

        UNION

        SELECT keyno, ebeln, ebelp, matnr, qty_a, qty_b, qty_total
          FROM ztxx0123
          WHERE ebelp = @i_ebelp

        UNION

        SELECT keyno, ebeln, ebelp, matnr, qty_a, qty_b, qty_total
          FROM ztxx0123
          WHERE matnr = @i_matnr
        INTO CORRESPONDING FIELDS OF TABLE @et_data.

* ===============================================================
* 2) 동적 WHERE — 문자열 조립, AND 로직
* ===============================================================
    WHEN '2'.
      DATA: lv_where TYPE string.

      IF i_keyno IS NOT INITIAL.
        IF lv_where IS NOT INITIAL.
          lv_where = lv_where && ' AND keyno = @i_keyno'.
        ELSE.
          lv_where = 'keyno = @i_keyno'.
        ENDIF.
      ENDIF.

      IF i_ebeln IS NOT INITIAL.
        IF lv_where IS NOT INITIAL.
          lv_where = lv_where && ' AND ebeln = @i_ebeln'.
        ELSE.
          lv_where = 'ebeln = @i_ebeln'.
        ENDIF.
      ENDIF.

      IF i_ebelp IS NOT INITIAL.
        IF lv_where IS NOT INITIAL.
          lv_where = lv_where && ' AND ebelp = @i_ebelp'.
        ELSE.
          lv_where = 'ebelp = @i_ebelp'.
        ENDIF.
      ENDIF.

      IF i_matnr IS NOT INITIAL.
        IF lv_where IS NOT INITIAL.
          lv_where = lv_where && ' AND matnr = @i_matnr'.
        ELSE.
          lv_where = 'matnr = @i_matnr'.
        ENDIF.
      ENDIF.

*     ★ 모든 매개변수가 비어있으면 lv_where 가 비어 전체 SELECT 위험 — 가드
      IF lv_where IS INITIAL.
        RETURN.
      ENDIF.

      SELECT *
        INTO CORRESPONDING FIELDS OF TABLE et_data
        FROM ztxx0123
        WHERE (lv_where).

* ===============================================================
* 3) RANGE + IN — ★ 권장 패턴
* ===============================================================
    WHEN '3'.
      DATA: r_keyno TYPE RANGE OF ekko-bedat,
            r_ebeln TYPE RANGE OF ekko-ebeln,
            r_ebelp TYPE RANGE OF ekpo-ebelp,
            r_matnr TYPE RANGE OF mara-matnr.

      IF i_keyno IS NOT INITIAL.
        APPEND VALUE #( sign = 'I' option = 'EQ' low = i_keyno ) TO r_keyno.
      ENDIF.
      IF i_ebeln IS NOT INITIAL.
        APPEND VALUE #( sign = 'I' option = 'EQ' low = i_ebeln ) TO r_ebeln.
      ENDIF.
      IF i_ebelp IS NOT INITIAL.
        APPEND VALUE #( sign = 'I' option = 'EQ' low = i_ebelp ) TO r_ebelp.
      ENDIF.
      IF i_matnr IS NOT INITIAL.
        APPEND VALUE #( sign = 'I' option = 'EQ' low = i_matnr ) TO r_matnr.
      ENDIF.

*     ★ 빈 RANGE 는 자동으로 모든 행 통과 — IF 분기 없이 깔끔
      SELECT *
        INTO CORRESPONDING FIELDS OF TABLE et_data
        FROM ztxx0123
        WHERE keyno IN r_keyno
          AND ebeln IN r_ebeln
          AND ebelp IN r_ebelp
          AND matnr IN r_matnr.

  ENDCASE.

* ※ 참고:
*   - 대부분 RANGE + IN 패턴이면 충분 (3번 모드)
*   - OR 합집합 필요할 때만 UNION
*   - 동적 WHERE 는 LIKE/BETWEEN 등 자유 조립 필요할 때만
*   - 빈 RANGE 의 IN = 모든 행 통과 (가장 큰 장점)
*   - 모든 매개변수 비어있을 때 전체 SELECT 방지 가드 권장

ENDFUNCTION.

요약

방법 키워드 언제
1 UNION OR 합집합 — "어느 하나라도 매칭" (찾기 기능)
2 동적 WHERE 문자열 조립 — LIKE/BETWEEN 등 자유 조건
3 RANGE + IN AND — 채워진 매개변수만 적용, 빈 RANGE 는 자동 무시

입력 매개변수가 여러 개고 각각이 비어있을 수도 있을 때, IF 분기로 16가지 경우를 나누는 건 좋지 않습니다.

RANGE + IN 패턴 이 90% 케이스를 깔끔하게 처리해주는 표준입니다 — 매개변수마다 RANGE 테이블을 두고, IS NOT INITIAL 일 때만 한 줄 APPEND 하고, SELECT 의 WHERE 에는 그냥 field IN range 라고 쓰면 끝.

빈 RANGE 의 IN 절은 모든 행 통과 라는 게 핵심 — IF 분기 줄어들고 코드가 짧아지고 매개변수가 늘어도 같은 패턴만 반복하면 됩니다. OR 합집합이 정말 필요할 때만 UNION, 자유로운 비교 연산자가 필요할 때만 동적 WHERE 로 가는 게 안전한 순서입니다.


Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.

UNION·동적 WHERE (lv_where)·RANGE + IN 은 ABAP Open SQL 표준 문법입니다.

UNION 과 인라인 변수(@VALUE #(...) 표현은 NetWeaver 7.40 이상에서 사용 가능합니다.

동적 WHERE 사용 시 사용자 입력값을 문자열에 직접 붙이지 말고 인라인 변수로만 바인딩해 SQL 인젝션을 방지해야 하며, 모든 매개변수가 비어있을 때 전체 테이블 SELECT 가 발생하지 않도록 가드(예: IF lv_where IS INITIAL. RETURN.) 를 추가하시기 바랍니다.