ABAP

아직도 LOOP에서 문자열 처리? SQL 함수 3가지 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 이번 예제에서 다루는 것

이 글에서는 ABAP Open SQL이 제공하는 내장 문자열 함수를 활용해 데이터베이스 레이어에서 직접 문자열을 가공하는 방법을 다룹니다. 전통적으로 ABAP 개발자는 SELECT로 데이터를 가져온 뒤 LOOP나 CONCATENATE, SHIFT, REPLACE 같은 ABAP 명령어로 후처리하는 패턴에 익숙합니다. 그러나 SAP NetWeaver AS ABAP 7.51 이후 강화된 Open SQL은 SQL 표현식 안에서 CONCAT, SUBSTRING, UPPER, LOWER, LENGTH, REPLACE, LPAD, LTRIM 등 다양한 함수를 그대로 사용할 수 있게 했습니다.

이 글을 끝까지 읽으면 다음을 할 수 있게 됩니다.

  • Open SQL 내장 문자열 함수 7종의 문법과 동작 원리 이해
  • ABAP 후처리 코드를 SQL 표현식으로 옮겨 성능 향상
  • 자재 코드 정규화, 주소 정제, 문서 번호 패턴 분리 같은 실무 시나리오 적용
  • NULL/공백/길이 초과 등 흔한 함정과 회피 패턴 숙지

이 글을 시작하기 전 알아두면 좋은 것

이 글은 ABAP Open SQL 기본 문법(SELECT, WHERE, INTO TABLE)과 인라인 선언(@DATA), 호스트 변수 표기(@) 정도를 알고 있다고 가정합니다. 또한 ABAP CDS View가 아닌 Open SQL 구문 안에서 사용하는 함수를 다루므로, ABAP Development Tools(ADT) 또는 SE38/SE80에서 코드를 실행할 수 있는 환경이면 충분합니다. CDS의 문자열 함수와 Open SQL의 함수는 이름이 거의 같지만 호출 위치가 다르다는 점만 기억하면 됩니다.

환경, 버전, 준비물

예제 코드는 다음 환경에서 검증을 권장합니다.

  • SAP NetWeaver AS ABAP 7.52 이상 또는 SAP S/4HANA 2020 이상 (일부 함수는 7.51부터 제공)
  • SAP HANA 또는 AnyDB (Oracle, DB2, MSSQL) — Open SQL 함수는 DB에 따라 내부 변환됨
  • ABAP Development Tools (Eclipse 기반) 또는 SAP GUI의 SE38
  • 테스트용 커스텀 테이블 ZSALES_ORDER 또는 표준 테이블 사용 권한
릴리스에 따라 지원 함수 목록이 다릅니다. 일반적으로 7.51에서는 LPAD, LENGTH, RIGHT 일부가 제한되며, 7.55 이후 대부분의 함수가 안정적으로 지원되는 것으로 알려져 있습니다. 사용 전에 시스템의 ABAP 릴리스를 SYSTEM > STATUS에서 확인하시기 바랍니다.

핵심 개념: SQL 레이어 문자열 처리의 의미

Open SQL 문자열 함수의 본질을 이해하려면 ABAP 애플리케이션 서버와 데이터베이스 서버 사이의 데이터 이동을 먼저 떠올려야 합니다. 전통적인 방식에서는 다음 흐름이 발생합니다.

  1. DB에서 원본 행 100만 건을 SELECT
  2. 네트워크를 통해 ABAP 서버로 전송
  3. ABAP 메모리에 적재 후 LOOP 돌며 CONCATENATE/REPLACE 등으로 가공
  4. 가공 결과를 다시 화면이나 다른 테이블에 기록

반면 SQL 문자열 함수를 사용하면 가공 단계가 DB 안에서 일어납니다. 이는 마치 식당에서 손님이 직접 재료를 받아 양념하는 대신, 주방장이 완성된 요리를 내어주는 것과 같습니다. 네트워크 트래픽이 줄고, 특히 HANA 같은 컬럼 스토어 DB에서는 코드 푸시다운(code pushdown)으로 인해 병렬 처리 효과까지 얻을 수 있습니다.

대표적인 Open SQL 문자열 함수는 다음과 같습니다.

함수역할예시 결과
CONCAT(a, b)두 문자열 연결'AB' + 'CD' → 'ABCD'
SUBSTRING(s, pos, len)부분 문자열 추출'ORDER-2024' → '2024'
UPPER / LOWER대소문자 변환'seoul' → 'SEOUL'
LENGTH(s)문자열 길이'SAP' → 3
REPLACE(s, old, new)치환'A-B-C' → 'A_B_C'
LPAD(s, len, pad)왼쪽 채우기'42' → '0000042'
LTRIM / RTRIM좌/우 공백 제거' abc ' → 'abc'

주의할 점은 Open SQL 함수의 인자는 문자형(CHAR, SSTRING) 컬럼이거나 호스트 변수여야 하며, NUMC 같은 숫자형 컬럼은 CAST(... AS CHAR(n))을 거쳐야 한다는 점입니다. 또한 SUBSTRING의 위치는 일반적으로 1부터 시작하며, 결과 타입은 입력 컬럼의 정의 길이에 따라 결정됩니다.

1단계 실전 예제: 기본 함수 익히기

가장 간단한 시나리오로 시작합니다. 영업 주문 테이블 ZSALES_ORDER에서 고객 코드를 대문자로 변환하고, 주문 번호 뒤 4자리만 추출하는 예제입니다.

REPORT zdemo_sql_string_basic.

DATA: lt_orders TYPE TABLE OF zsales_order.

" SQL 함수로 변환·추출을 한 번에 수행
SELECT
    order_id,
    UPPER( customer_code )                AS cust_upper,
    SUBSTRING( order_id, 7, 4 )           AS order_seq,
    LENGTH( delivery_addr )               AS addr_len
  FROM zsales_order
  INTO TABLE @DATA(lt_result)
  UP TO 50 ROWS.

LOOP AT lt_result ASSIGNING FIELD-SYMBOL(<ls>).
  WRITE: / <ls>-order_id,
           <ls>-cust_upper,
           <ls>-order_seq,
           <ls>-addr_len.
ENDLOOP.

여기서 핵심은 SELECT 절 안에서 UPPER, SUBSTRING, LENGTH가 직접 호출된다는 점입니다. ABAP에서 별도의 변환 루프를 작성하지 않아도 결과 내부 테이블에는 이미 가공된 문자열이 들어 있습니다. AS 별칭은 결과 컬럼 이름으로, 인라인 선언된 lt_result의 컴포넌트 이름이 됩니다.

2단계 실전 예제: 실무 시나리오 — 자재 코드 정규화와 주소 정제

실제 프로젝트에서는 함수들을 조합해 정규화 로직을 SQL 안에 녹입니다. 다음은 자재 마스터의 일관성 없는 입력을 표준 형식으로 변환하면서 로그를 함께 남기는 예제입니다. 자재 코드는 좌측 0 패딩 10자리로, 납품 주소는 공백을 제거하고 하이픈을 언더스코어로 변환합니다.

REPORT zdemo_sql_string_normalize.

CONSTANTS: c_pad_len TYPE i VALUE 10.

TRY.
    SELECT
        matnr_raw,
        LPAD( matnr_raw, @c_pad_len, '0' )                AS matnr_norm,
        REPLACE( RTRIM( LTRIM( delivery_addr ) ),
                 '-', '_' )                                AS addr_clean,
        CONCAT( CONCAT( plant_code, '/' ),
                storage_loc )                              AS location_key
      FROM zmat_inbound
      WHERE created_on >= @( cl_abap_context_info=>get_system_date( ) - 7 )
      INTO TABLE @DATA(lt_norm).

    IF lt_norm IS INITIAL.
      MESSAGE 'No inbound material in the last 7 days' TYPE 'I'.
    ELSE.
      cl_demo_output=>display( lt_norm ).
      WRITE: / |{ lines( lt_norm ) } rows normalized|.
    ENDIF.

  CATCH cx_sy_open_sql_db INTO DATA(lx_db).
    MESSAGE lx_db->get_text( ) TYPE 'E'.
ENDTRY.

이 코드의 포인트는 세 가지입니다. 첫째, LPAD로 자재 코드를 표준 10자리로 맞추어 후속 BAPI나 인터페이스에서 발생하는 길이 오류를 사전에 차단합니다. 둘째, LTRIM/RTRIM/REPLACE를 중첩해 호출하여 주소 컬럼의 입력 노이즈를 한 번의 SELECT에서 제거합니다. 셋째, CONCAT을 중첩해 plant_code와 storage_loc 사이에 구분자를 삽입하는데, 일부 함수는 인자를 두 개만 받기 때문에 셋 이상은 중첩으로 풀어야 합니다.

3단계 실전 예제: 프로덕션 — 성능, 안전, 단위 테스트

운영 환경에서는 성능 모니터링, NULL 안전성, 그리고 단위 테스트가 필수입니다. 다음 예제는 대량 문서 번호에서 연도와 일련번호를 분리하면서, GROUP BY와 함께 사용해 집계까지 한 번에 수행하는 패턴입니다.

CLASS zcl_doc_number_analyzer DEFINITION PUBLIC FINAL.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_summary,
             doc_year TYPE c LENGTH 4,
             doc_cnt  TYPE i,
           END OF ty_summary,
           tt_summary TYPE STANDARD TABLE OF ty_summary WITH EMPTY KEY.

    METHODS aggregate_by_year
      IMPORTING iv_company TYPE bukrs
      RETURNING VALUE(rt_summary) TYPE tt_summary
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_doc_number_analyzer IMPLEMENTATION.
  METHOD aggregate_by_year.
    " 문서번호 패턴: 'INV-2024-000123' → 연도 부분만 추출 후 집계
    SELECT
        SUBSTRING( doc_number, 5, 4 )       AS doc_year,
        COUNT(*)                            AS doc_cnt
      FROM zfi_document
      WHERE company_code = @iv_company
        AND LENGTH( doc_number ) >= 8
      GROUP BY SUBSTRING( doc_number, 5, 4 )
      ORDER BY doc_year DESCENDING
      INTO CORRESPONDING FIELDS OF TABLE @rt_summary.
  ENDMETHOD.
ENDCLASS.

단위 테스트는 ABAP Unit으로 다음과 같이 작성할 수 있습니다.

CLASS ltc_analyzer DEFINITION FOR TESTING
  RISK LEVEL HARMLESS DURATION SHORT FINAL.
  PRIVATE SECTION.
    METHODS test_year_split FOR TESTING.
ENDCLASS.

CLASS ltc_analyzer IMPLEMENTATION.
  METHOD test_year_split.
    DATA(lo) = NEW zcl_doc_number_analyzer( ).
    DATA(lt) = lo->aggregate_by_year( '1000' ).
    cl_abap_unit_assert=>assert_not_initial( lt ).
    LOOP AT lt INTO DATA(ls).
      cl_abap_unit_assert=>assert_char_cp(
        act = ls-doc_year
        exp = '++++' ).
    ENDLOOP.
  ENDMETHOD.
ENDCLASS.

이 단계에서 챙겨야 할 점은 다음과 같습니다. GROUP BY 절에도 동일한 SUBSTRING 표현식을 그대로 반복해야 하며, 별칭(alias)으로는 GROUP BY가 불가능한 경우가 많습니다. 또한 LENGTH 조건으로 패턴 미달 행을 사전에 제외해 SUBSTRING이 빈 문자열을 반환하는 상황을 방지합니다. 성능 측면에서는 ST05 SQL Trace로 실제 DB에 푸시다운된 SQL을 확인해 함수가 DB 네이티브로 변환되었는지 검증하는 것이 권장됩니다.

흔한 실수와 트러블슈팅

SQL 문자열 함수를 처음 도입할 때 자주 마주치는 함정들을 FAQ로 정리합니다.

Q1. SUBSTRING의 길이를 변수로 주면 덤프가 납니다.
일부 릴리스에서 SUBSTRING의 pos, len 인자는 리터럴이거나 호스트 변수여야 합니다. 호스트 변수는 반드시 @ 접두사를 붙여 SUBSTRING( field, @lv_pos, @lv_len ) 형태로 전달해야 합니다. 별칭이나 다른 컬럼을 길이로 사용하면 CX_SY_OPEN_SQL_DB가 발생할 수 있습니다.

Q2. LENGTH 결과가 예상보다 큽니다.
LENGTH는 컬럼의 정의 길이가 아닌 실제 문자 길이를 반환하지만, CHAR 컬럼은 트레일링 공백 처리 방식이 DB마다 다를 수 있습니다. 정확한 길이가 필요하다면 LENGTH( RTRIM( field ) )처럼 RTRIM과 조합하는 것이 일반적입니다.

Q3. NUMC 필드에 CONCAT을 쓰면 오류가 납니다.
Open SQL 문자열 함수는 문자형 인자를 기대합니다. NUMC, INT4 등 숫자형은 CAST( field AS CHAR( 10 ) )로 명시적 변환 후 CONCAT에 넘기는 것을 권장합니다. 무언의 변환은 DB 종속적이라 이식성이 떨어집니다.

Q4. HANA에서는 동작하는데 다른 DB에서 실패합니다.
Open SQL은 추상화 레이어지만, REPLACE의 다중 치환이나 LPAD의 패딩 문자 길이 같은 세부 동작은 DB별로 차이가 있을 수 있습니다. 멀티 DB 환경에서는 함수 사용 후 ST05로 변환된 네이티브 SQL을 확인하고, 가능하면 표준 케이스만 사용하는 것이 안전합니다.

이어서 탐색하면 좋은 주제

Open SQL 문자열 함수에 익숙해졌다면 다음 영역으로 확장해보시기 바랍니다. 첫째, ABAP CDS View 안에서 동일한 함수들을 정의해 재사용 가능한 시맨틱 뷰를 만드는 것입니다. 둘째, CASE WHEN과 결합해 조건부 문자열 가공을 SQL 한 줄로 처리하는 패턴입니다. 셋째, AMDP(ABAP Managed Database Procedure)로 HANA SQLScript의 더 강력한 문자열 함수(LOCATE_REGEXPR, OCCURRENCES_REGEXPR 등)를 활용하는 단계입니다. 마지막으로 RAP(RESTful Application Programming Model)에서 계산 필드로 문자열 함수를 노출해 Fiori UI에 직접 표시하는 응용도 권장됩니다.

댓글 0

아직 댓글이 없습니다.