From Zero to SAP

[실습] ABAP 인터널 테이블 실습 - 선언부터 집계, 조건 처리, 출력까지 실전 연습 본문

ABAP/실습

[실습] ABAP 인터널 테이블 실습 - 선언부터 집계, 조건 처리, 출력까지 실전 연습

권쌥 2026. 4. 12. 19:03

피트니스 센터에는 다음 두 개의 DB 테이블이 있다.

회원 테이블: ZMEMBER
필드: MBR_ID(CHAR10), MBR_NAME(CHAR30), GRADE(CHAR10), JOIN_DATE(DATS)

출석 테이블: ZATTEND
필드: MBR_ID(CHAR10), ATTEND_DATE(DATS), LESSON_FEE(INT4)`


[테이블 생성]

@EndUserText.label : '피트니스 회원 마스터'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zmember {

  key client : abap.clnt not null;
  key mbr_id : abap.char(10) not null;
  mbr_name   : abap.char(30);
  grade      : abap.char(10);
  join_date  : abap.dats;

}
@EndUserText.label : '피트니스 출석 이력'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zattend {

  key client      : abap.clnt not null;
  key mbr_id      : abap.char(10) not null;
  key attend_date : abap.dats not null;
  lesson_fee      : abap.int4;

}

[CDS VIEW 생성]

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'member CDS view'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZI_MEMBER as select from zmember
{
    key mbr_id,
    mbr_name,
    grade,
    join_date
}
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'attend cds view'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZI_ATTEND as select from zattend 
association [1..1] to ZI_MEMBER as _Member -- ZI_ATTEND에서 ZI_MEMBER로 가는 길을 _Member라는 이름으로 뚫어놓은 
on $projection.mbr_id = _Member.mbr_id -- JOIN 조건 (이 뷰의 mbr_id랑 ZI_MEMBER의 mbr_id가 같을 때 연결해)

{
    key mbr_id,
    key attend_date,
        lesson_fee,
        _Member  -- ← association 노출 (ZI_ATTEND._Member.grade 이렇게 접근 가능하도록)
}

ZI_ATTEND에서 ZI_MEMBER로의 Association을 선언해서, mbr_id를 기준으로 회원 정보를 탐색할 수 있는 네비게이션 경로를 구성했다.


[1단계] 데이터 셋업

인터널 테이블 선언부터 데이터 입력까지

  • gt_member: ZMEMBER 구조 기반, MBR_ID로 해시 탐색이 가능한 인터널 테이블로 선언
  • gt_attend: ZATTEND 구조 기반, MBR_ID + ATTEND_DATE로 정렬 상태를 유지하는 인터널 테이블로 선언

gt_member는 아래와 같이 5명의 데이터를 VALUE #( ) 방식으로 한 번에 초기화했다.

MBR_ID MBR_NAME GRADE JOIN_DATE
M001 김철수 GOLD 20230101
M002 이영희 SILVER 20230315
M003 박민준 GOLD 20230601
M004 최수진 BRONZE 20230901
M005 정태양 SILVER 20240101

 

gt_attend는 아래와 같이 출석 데이터를 추가했다.

이 때, VALUE #( ) 방식이 아닌 INSERT 방식을 고려했다.

이유는 아래와 같다.

1. gt_attend는 Sorted Table로 선언되었다.

SORTED TABLE은 항상 정렬 상태를 유지해야 하기 때문에, = 로 통째 할당하면 정렬 키 중복 체크 등 테이블 타입의 제약을 우회해버림.

그래서 ABAP에서 아예 런타임 에러 또는 컴파일 경고로 막아버린다.

2. 실무적인 관점에서 보면, 출석 데이터는 기존 데이터에서 추가하는 경우가 많기 때문이다.

 

INSERT는 인터널 테이블 타입에 따라서 동작이 디테일이 다르다.

(참고로, SORTED TABLE에서 APPEND로 데이터를 추가하면 정렬이 깨진다. HASHED TABLE에서는 APPEND를 많이 사용한다.

'이 테이블은 순서 없는 해시구나' 라는 의도가 코드에서 보이기 때문에)

STANDARD APPEND랑 똑같이 맨 뒤에 추가
SORTED 정렬 키 기준으로 위치 찾아서 삽입
HASHED 해시 키로 버킷 계산해서 삽입
MBR_ID ATTEND_DATE LESSON_FEE
M001 20240101 50000
M001 20240115 50000
M001 20240201 60000
M002 20240101 30000
M002 20240201 30000
M003 20240101 50000
M003 20240115 70000
M005 20240201 40000


[2단계] 집계

  • gt_fee_sum: MBR_ID별 LESSON_FEE 합계를 담는 인터널 테이블 만들기

  • gt_attend를 LOOP 돌면서 COLLECT로 MBR_ID별 총 수강료를 집계하기

처음에 COLLECT ls_attend INTO gt_fee_sum 을 시도했으나 실패했다.

그 이유는 ls_attend는 gt_attend 테이블의 한 행을 가져오는데 gt_fee_sum와 타입이 다르기 때문이다.

따라서, 필요한 필드만 골라서 ty_fee_sum 타입으로 만든 다음 gt_fee_sum에 넣었다.


[3단계] 데이터 보강 및 조건 처리

  • gt_fee_sum을 LOOP 돌면서, 각 MBR_ID에 해당하는 회원 이름을 gt_member에서 읽어와서 채우기
    이 때 BINARY SEARCH는 쓰면 안 되고, 대신 gt_member의 테이블 타입 특성을 활용해 O(1)로 읽기

gt_member는 HASHED TABLE이다. 해시 테이블에서 KEY로 READ하면 자동으로 O(1)이다.

첫 번째 LOOP에서는 출석 테이블을 돌면서 인터널 테이블  gt_fee_sum에 멤버ID를 기준으로 레슨 비용 합산을 저장한다.

두 번째 LOOP에서는 위에서 계산한 테이블을 한 행씩 돌면서 멤버 ID를 기준으로 gt_member 테이블에서 해당 멤버의 정보를 가져와서 ls_member에 저장한다. 마지막으로, 출력 결과에서 보여줄 필드를 APPEND한다.

COLLECT는 자동으로 문자 타입 필드는 키로 판단하고, 숫자 타입 필드는 합산 대상으로 판단하여 계산한다.

 

  • 총 수강료가 100,000 이상인 회원은 GRADE를 'VIP'로 업데이트하기 (gt_member 기준)
    MODIFY 시 GRADE 필드만 수정되도록 작성

TRANSPORTING 키워드를 사용하여 grade 필드만 수정되도록 하였다.


[4단계] 출력

  • gt_member를 JOIN_DATE 오름차순으로 정렬한 뒤 출력하기
    출력 형식: [순번] MBR_ID | MBR_NAME | GRADE | JOIN_DATE
  • M004 (최수진)처럼 출석 기록이 없는 회원도 정상 출력되도록 하기