이 글에서 다루는 내용
ABAP 7.40 SP05부터 도입된 @DATA(...) 인라인 선언은 Open SQL의 SELECT 결과를 받기 위해 미리 작업 영역(work area)이나 내부 테이블(internal table) 타입을 선언해야 했던 전통적인 방식을 크게 단순화합니다. 이 글에서는 실제 영업 주문(SalesOrder), 구매 주문(PurchaseOrder), 고객 마스터(CustomerMaster) 데이터를 대상으로 인라인 선언이 어떻게 동작하고 어떤 타입이 자동 추론되는지, 그리고 실무 코드에서 어떤 함정에 주의해야 하는지를 단계별로 살펴봅니다.
- 전통적
TYPES+DATA패턴과@DATA()패턴의 비교 이해 - 단일 행, 다중 행, JOIN/Aggregation 결과의 인라인 수신 방법
- 호스트 변수 이스케이프 문자
@의 역할과 새 ABAP SQL 문법 요구사항 - 인라인 변수의 스코프(scope), 디버깅, 테스트 시 주의점
읽기 전에 알고 있으면 좋은 것
이 글은 ABAP의 DATA, TYPES, SELECT ... INTO 같은 기본 문법에 익숙한 개발자를 가정합니다. 내부 테이블의 종류(STANDARD, SORTED, HASHED)와 작업 영역(work area)의 개념, 그리고 DDIC(Data Dictionary)에 정의된 테이블 구조에 대한 기초 지식이 있으면 인라인 선언이 추론하는 타입의 의미를 더 빠르게 이해할 수 있습니다.
실습 환경 및 버전
@DATA() 인라인 선언은 ABAP 언어 버전 7.40 SP05에 처음 도입되었고, 이후 7.50, 7.52, ABAP Platform 2020, S/4HANA 기반 ABAP Cloud(릴리스 2023 이상)에서 점차 확장되어 왔습니다. 이 글의 예제는 다음 환경을 가정합니다.
- NetWeaver 7.52 또는 S/4HANA 2022 이상의 ABAP 백엔드
- ADT(ABAP Development Tools) for Eclipse 또는 SE80/SE38
- 표준 DDIC 테이블 또는 Z 테이블(
zsales_order,zpur_order,zcust_mast)에 적절한 권한 - 새 ABAP SQL 문법을 활성화하기 위해 호스트 변수 앞에
@사용
일부 구문은 ABAP Cloud / RAP(ABAP RESTful Application Programming Model)에서도 동일하게 동작하지만, 클라우드 환경에서는 릴리스 제한된 DDIC 객체만 접근할 수 있으므로 권한과 릴리스 상태를 함께 확인하는 것이 일반적으로 권장됩니다.
인라인 선언의 동작 원리와 타입 추론
전통적으로 ABAP에서 SELECT 결과를 받으려면 컴파일러가 결과의 구조를 알 수 있도록 미리 작업 영역이나 내부 테이블을 DATA 문으로 선언해야 했습니다. @DATA(lv_x) 또는 @DATA(lt_x)는 이 단계를 컴파일 시점에 자동으로 처리합니다. 컴파일러는 SELECT 절의 필드 목록과 대상 데이터베이스 테이블/CDS 뷰의 구조를 분석해, 변수를 적절한 기본 타입(elementary), 구조(structure), 표준 내부 테이블(standard internal table)로 정적으로 추론합니다.
비유하자면, 기존 방식이 "옷을 입을 사람의 사이즈를 미리 재서 옷장을 마련해 두는 것"이라면, 인라인 선언은 "옷이 결정되는 순간 옷장이 같은 모양으로 자동 생성되는 것"에 가깝습니다. 즉, 변수의 타입은 SELECT 문 자체가 결정하는 일종의 "역방향 타입 추론"입니다.
중요한 규칙 몇 가지를 정리하면 다음과 같습니다.
SELECT SINGLE ... INTO @DATA(ls_x)→ls_x는 SELECT 필드 목록에 맞춘 평탄 구조(flat structure)SELECT ... INTO TABLE @DATA(lt_x)→lt_x는 표준 키 없는(STANDARD TABLE WITH EMPTY KEY) 내부 테이블SELECT SINGLE single_field ... INTO @DATA(lv_x)→lv_x는 해당 필드의 elementary 타입@DATA()안의 변수는 선언과 동시에 사용되므로, 같은 블록(form, method, 이벤트) 안에서만 유효합니다.
또한 새 ABAP SQL 문법에서는 호스트 변수 앞에 반드시 @를 붙여야 합니다. 예를 들어 WHERE order_id = @lv_order_id처럼 작성해야 하며, 이를 빠뜨리면 컴파일 단계에서 구문 오류가 발생합니다. 인라인 선언과 호스트 변수 접두어 @는 새 SQL 문법 패키지의 일부로 함께 도입되었기 때문에 둘은 사실상 짝으로 사용됩니다.
1단계 — 단일 영업 주문 헤더를 인라인으로 받기
가장 간단한 시나리오부터 보겠습니다. 영업 주문 번호로 헤더 한 건을 조회해 금액과 통화를 표시하는 코드입니다. 먼저 기존 방식과 인라인 선언 방식을 같은 결과를 내도록 작성해 비교합니다.
" 기존 방식 - 작업 영역을 명시적으로 선언해야 함
DATA: BEGIN OF ls_order_old,
order_id TYPE zsales_order-order_id,
customer TYPE zsales_order-customer,
net_amount TYPE zsales_order-net_amount,
currency TYPE zsales_order-currency,
END OF ls_order_old.
SELECT SINGLE order_id customer net_amount currency
FROM zsales_order
INTO ls_order_old
WHERE order_id = '4500001234'.
IF sy-subrc = 0.
WRITE: / ls_order_old-order_id, ls_order_old-net_amount, ls_order_old-currency.
ENDIF.
같은 동작을 @DATA()로 다시 쓰면 사전 DATA 블록이 사라집니다.
" 인라인 선언 방식 - 새 ABAP SQL 문법
SELECT SINGLE order_id, customer, net_amount, currency
FROM zsales_order
WHERE order_id = '4500001234'
INTO @DATA(ls_order).
IF sy-subrc = 0.
WRITE: / ls_order-order_id, ls_order-net_amount, ls_order-currency.
ENDIF.
여기서 ls_order는 네 개 필드를 가진 평탄 구조로 자동 추론됩니다. 새 문법에서는 필드 목록을 콤마(,)로 구분하고, INTO 절이 WHERE보다 뒤에 오는 형태가 일반적으로 사용됩니다.
2단계 — 다중 주문 + 에러/로깅을 포함한 실무 시나리오
실무에서는 단일 행보다 다중 행 조회와 에러 처리, 로깅이 함께 필요합니다. 특정 고객의 최근 영업 주문 목록을 가져와 합계를 계산하는 예제를 살펴보겠습니다. 인라인 선언으로 내부 테이블을 받고, 결과가 비어있는 경우와 SQL 예외 상황을 명확히 분리합니다.
METHOD get_recent_orders.
" 입력: iv_customer TYPE zcust_mast-customer
" 출력: et_result TYPE STANDARD TABLE OF ...
TRY.
SELECT order_id, order_date, net_amount, currency, status
FROM zsales_order
WHERE customer = @iv_customer
AND order_date >= @( cl_abap_context_info=>get_system_date( ) - 30 )
ORDER BY order_date DESCENDING
INTO TABLE @DATA(lt_orders).
IF lt_orders IS INITIAL.
" 비어있는 결과는 예외가 아닌 정상 상황으로 처리
MESSAGE i001(zsales) WITH iv_customer.
RETURN.
ENDIF.
" 합계 계산 - lt_orders 의 행 타입은 컴파일러가 추론
DATA(lv_total) = REDUCE zsales_order-net_amount(
INIT sum TYPE zsales_order-net_amount
FOR <fs> IN lt_orders
NEXT sum = sum + <fs>-net_amount ).
" 호출자에게 결과 전달
et_result = lt_orders.
ev_total = lv_total.
cl_appl_log_writer=>info(
iv_object = 'ZSALES'
iv_subobject = 'ORDER_READ'
iv_text = |Customer { iv_customer } orders: { lines( lt_orders ) }| ).
CATCH cx_sy_open_sql_db INTO DATA(lo_sql_ex).
cl_appl_log_writer=>error(
iv_object = 'ZSALES'
iv_text = lo_sql_ex->get_text( ) ).
RAISE EXCEPTION TYPE zcx_sales_read
EXPORTING previous = lo_sql_ex.
ENDTRY.
ENDMETHOD.
이 코드에는 인라인 선언을 실무적으로 활용하는 세 가지 포인트가 있습니다. 첫째, @DATA(lt_orders)는 표준 키 없는 내부 테이블로 추론되어 별도 TYPES 정의가 불필요합니다. 둘째, REDUCE 표현식 안에서 DATA(lv_total)도 인라인 선언되어 합계 타입까지 한 줄로 처리됩니다. 셋째, CATCH ... INTO DATA(lo_sql_ex)처럼 예외 객체도 같은 방식으로 받을 수 있어 코드 흐름이 위에서 아래로 일관되게 읽힙니다.
3단계 — 프로덕션 품질: JOIN, 집계, 그리고 테스트 가능성
마지막 단계에서는 다중 테이블 JOIN과 집계, 그리고 단위 테스트에서 신경 써야 할 부분을 함께 살펴봅니다. 시나리오는 "구매 주문(PurchaseOrder)별 공급사명과 라인 합계를 한 번의 쿼리로 얻기"입니다.
CLASS zcl_po_reader IMPLEMENTATION.
METHOD read_po_summary.
SELECT h~po_number,
h~vendor,
v~name1 AS vendor_name,
SUM( i~net_value ) AS total_value,
h~currency
FROM zpur_order AS h
INNER JOIN lfa1 AS v
ON v~lifnr = h~vendor
INNER JOIN zpur_item AS i
ON i~po_number = h~po_number
WHERE h~created_on BETWEEN @iv_from AND @iv_to
AND h~status = @zif_po_const=>status_released
GROUP BY h~po_number, h~vendor, v~name1, h~currency
ORDER BY h~po_number
INTO TABLE @DATA(lt_summary).
" 인라인 선언된 lt_summary 의 구조는 SELECT 절이 결정.
" vendor_name, total_value 같은 alias 도 필드명으로 그대로 반영됨.
LOOP AT lt_summary INTO DATA(ls_row).
APPEND VALUE #(
po_number = ls_row-po_number
vendor_name = ls_row-vendor_name
total_value = ls_row-total_value
currency = ls_row-currency
) TO rt_result.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
이 예제에서 주목할 점은 alias로 정의된 vendor_name과 집계 함수 결과인 total_value가 그대로 인라인 구조의 필드명이 된다는 것입니다. 즉, SELECT 절 자체가 타입을 명세하는 단일 진실 공급원(single source of truth) 역할을 합니다.
테스트 측면에서는 두 가지를 권장합니다. 첫째, ABAP Unit과 함께 CDS Test Double Framework 또는 OSQL Test Double을 사용해 데이터베이스 접근을 격리하면, 인라인 선언으로 단순화된 코드도 안정적으로 테스트할 수 있습니다. 둘째, 동일 SELECT를 재사용한다면 인라인 변수보다는 명시적 타입을 두는 것이 인터페이스 안정성에 유리하므로, 외부 노출되는 메서드 시그니처에는 TYPES로 정의된 명시적 행 타입을 권장합니다.
자주 마주치는 실수와 해결 방법
인라인 선언은 강력하지만 다음과 같은 실수가 반복적으로 보고됩니다.
- 호스트 변수에
@누락:WHERE customer = iv_customer처럼@를 빠뜨리면 새 SQL 문법에서 구문 오류가 발생합니다. 해결: 모든 ABAP 변수 앞에@를 붙입니다. - 인라인 변수 스코프 오해:
IF또는LOOP블록 안에서@DATA()로 선언한 변수는 해당 블록 종료 후에도 유효하지만, 폼/메서드 경계를 넘어가지는 않습니다. 호출자에게 반환해야 한다면 명시적 EXPORTING/RETURNING 파라미터로 옮겨야 합니다. - 키 없는 내부 테이블의 성능 함정:
INTO TABLE @DATA(lt_x)는 STANDARD TABLE WITH EMPTY KEY로 추론됩니다. 이후READ TABLE ... WITH KEY를 빈번히 호출하면 선형 탐색이 발생하므로, 키 접근이 잦은 경우TYPES로 SORTED/HASHED 테이블을 명시 선언하는 것이 일반적으로 더 적절합니다.
FAQ
- Q.
SELECT * INTO @DATA(ls_x)처럼 별표를 써도 되나요?
A. 동작은 하지만 권장되지 않습니다. 테이블 구조가 바뀌면 의도하지 않은 필드까지 끌어와 메모리/네트워크 비용이 증가하고, 코드 리뷰에서 의도 파악이 어려워집니다. 필요한 컬럼만 명시적으로 나열하는 것을 권장합니다. - Q. 인라인 선언된 변수를 디버거에서 확인할 수 있나요?
A. 네. ADT 디버거와 SAP GUI 디버거 모두 인라인 선언 변수를 일반 변수와 동일하게 표시합니다. 다만 변수가 실제로 "존재"하기 시작하는 시점은 SELECT 문 실행 직후이므로, 그 이전 라인에 중단점을 두면 값이 비어있는 것처럼 보일 수 있습니다. - Q. CDS 뷰를 SELECT할 때도 동일하게 동작하나요?
A. 네, CDS 뷰의 엔터티 구조를 기반으로 동일하게 추론됩니다. 다만 ABAP Cloud에서는 릴리스된 CDS만 접근 가능하므로@AccessControl과 릴리스 컨트랙트를 함께 확인해야 합니다.
이어서 살펴보면 좋은 주제
인라인 선언을 충분히 익혔다면, ABAP 7.40 이후 도입된 다른 표현식 기반 문법으로 자연스럽게 확장할 수 있습니다. 생성자 표현식 VALUE #( ... ), 테이블 표현식 tab[ ... ], FOR ... IN 루프 표현식, 그리고 NEW를 활용한 객체 인라인 생성이 대표적입니다. 또한 Open SQL을 넘어 RAP의 EML(Entity Manipulation Language)과 CDS 뷰 설계, ABAP SQL의 WITH(공통 테이블 식) 패턴까지 학습 범위를 넓히면, 인라인 선언이 가져다 준 간결함을 더 큰 비즈니스 로직에 일관되게 적용할 수 있습니다.
더 읽어볼 만한 자료
댓글 0
아직 댓글이 없습니다.