ABAP SQL 신기능 완전 가이드 — Window Functions, CTE, 계층 쿼리까지

AI News · 조회 1

ABAP SQL 신기능 타이틀

핵심 3가지

CTE + Window 조합 코드

ABAP SQL 신기능 완전 가이드 — Window Functions, CTE, 계층 쿼리까지

1. 개요 및 학습 목표

ABAP SQL은 7.51 릴리스 이후 대폭 강화되어, 기존에 애플리케이션 레이어에서 루프로 처리하던 복잡한 집계·순위·계층 연산을 데이터베이스 레벨에서 선언적으로 수행할 수 있게 되었습니다. 이 가이드에서는 CTE(Common Table Expressions), Window Functions, 그리고 계층 쿼리(WITH HIERARCHY)를 실무 코드와 함께 단계별로 학습합니다.

학습 완료 후 체크리스트:

2. 선수 지식

이 튜토리얼을 효과적으로 따라가려면 다음 사항에 대한 기본 이해가 필요합니다.

3. 환경 / 버전 / 준비물

ABAP SQL의 고급 기능은 릴리스마다 점진적으로 추가되었습니다. 아래 표를 참고하여 사용 중인 시스템의 지원 범위를 확인하세요.

기능최소 요구 버전비고
CTE (WITH 절)ABAP 7.51기본 CTE, 다단계 체이닝
ROW_NUMBER, RANK, DENSE_RANKABAP 7.51Ranking Functions
LEAD, LAGABAP 7.54Value Functions, 디폴트 값 지정 가능
FIRST_VALUE, LAST_VALUEABAP 7.54프레임 경계 지정 권장
NTILEABAP 7.54균등 분할 버켓팅
WITH HIERARCHYABAP 7.54계층 쿼리, 부모-자식 모델

준비물:

4. 핵심 개념

CTE — 임시 이름표를 붙인 서브쿼리

CTE는 WITH 키워드로 시작하는 이름 있는 임시 뷰입니다. 비유하자면, 긴 보고서를 작성할 때 중간 결과를 별도의 메모 카드에 적어두고, 최종 보고서에서 해당 카드를 참조하는 방식과 유사합니다. 각 CTE는 데이터베이스 접근 기간 동안만 존재하며, 후속 쿼리에서 테이블처럼 참조할 수 있습니다.

CDS View vs CTE: CTE는 한 번의 SQL 실행 내에서만 유효한 임시 뷰입니다. 여러 프로그램에서 재사용해야 한다면 CDS View가 더 적합합니다. 단일 리포트 내 복잡한 집계에는 CTE가 코드 가독성 면에서 유리합니다.

Window Functions — 행을 유지한 채로 분석하기

일반 집계(GROUP BY)는 행을 그룹으로 축소합니다. 반면 Window Function은 원본 행을 그대로 유지하면서 각 행에 분석 결과를 덧붙입니다. 비유하자면, 교실에서 학생들이 줄 서 있을 때 GROUP BY는 "반별 평균 키"만 남기지만, Window Function은 "각 학생 옆에 반 평균 키 스티커를 붙여주는 것"입니다.

Window Function은 세 가지 범주로 나뉩니다.

핵심 구문 요소는 다음과 같습니다.

계층 쿼리 — 트리 구조를 SQL로 펼치기

조직도, BOM(자재 명세서), 계정 코드 체계처럼 부모-자식 관계를 갖는 데이터를 다룰 때, 기존에는 ABAP 루프로 재귀 탐색해야 했습니다. ABAP 7.54부터 WITH HIERARCHY 구문을 사용하면 선언적으로 계층 구조를 탐색할 수 있습니다. 참고로 SAP HANA의 WITH 절은 재귀 쿼리를 지원하지 않으므로, 계층 데이터에는 이 전용 구문을 사용하는 것이 일반적입니다.

5. 실전 코드 3단계

1단계: CTE 기본 — WITH, 다단계 집계, JOIN

판매 오더 헤더(VBAK)와 아이템(VBAP)을 CTE로 분리 집계한 뒤 JOIN하는 패턴입니다. 복잡한 서브쿼리를 중첩하는 대신, 각 단계를 명명된 CTE로 분리하여 가독성을 높입니다.

" ABAP 7.51+: CTE 기본 — 판매 오더 분석
DATA lv_from_date TYPE dats VALUE '20260101'.

WITH
  " 1단계 CTE: 기간 내 판매 오더 헤더 필터링
  +cte_sales AS (
    SELECT FROM vbak
    FIELDS vbeln, kunnr, erdat, auart
    WHERE erdat >= @lv_from_date
  ),
  " 2단계 CTE: 아이템별 금액·수량 집계
  +cte_items AS (
    SELECT FROM vbap
    FIELDS vbeln,
           SUM( netwr ) AS total_amount,
           COUNT( * )   AS item_count
    GROUP BY vbeln
  ),
  " 3단계 CTE: 고객별 주문 건수 집계
  +cte_cust_summary AS (
    SELECT FROM +cte_sales AS s
      INNER JOIN +cte_items AS i ON i~vbeln = s~vbeln
    FIELDS s~kunnr,
           COUNT( * )       AS order_count,
           SUM( i~total_amount ) AS cust_total
    GROUP BY s~kunnr
  )
  " 최종 SELECT: CTE 결과를 조합
  SELECT FROM +cte_sales AS s
    INNER JOIN +cte_items AS i ON i~vbeln = s~vbeln
    LEFT OUTER JOIN +cte_cust_summary AS c ON c~kunnr = s~kunnr
  FIELDS s~vbeln,
         s~kunnr,
         s~erdat,
         i~total_amount,
         i~item_count,
         c~order_count,
         c~cust_total
  INTO TABLE @DATA(lt_result).

cl_demo_output=>display( lt_result ).

포인트: CTE 이름 앞에 + 접두사를 붙이는 것이 ABAP SQL 구문 규칙입니다. 호스트 변수(@lv_from_date)를 활용하여 동적으로 필터링 조건을 전달합니다. 각 CTE는 이전 CTE를 참조할 수 있어 단계별 집계가 자연스럽게 이어집니다.

2단계: Window Functions — ROW_NUMBER, RANK, LEAD/LAG, 집계 윈도우

직원 테이블에서 부서별 급여 순위를 매기고, 전후 행 값을 비교하는 예제입니다.

" ABAP 7.54+: Window Functions 종합 예제
SELECT FROM zhr_employees
FIELDS
  empid,
  department,
  name,
  salary,

  " 부서별 급여 내림차순 일련번호 (동일 값이어도 고유 번호)
  ROW_NUMBER( ) OVER(
    PARTITION BY department
    ORDER BY salary DESCENDING
  ) AS row_num,

  " 부서별 급여 순위 (동일 값이면 같은 순위, 다음 순위 건너뜀)
  RANK( ) OVER(
    PARTITION BY department
    ORDER BY salary DESCENDING
  ) AS salary_rank,

  " 부서별 급여 순위 (동일 값이면 같은 순위, 다음 순위 연속)
  DENSE_RANK( ) OVER(
    PARTITION BY department
    ORDER BY salary DESCENDING
  ) AS dense_rnk,

  " 같은 파티션 내 다음 행의 급여 (없으면 0)
  LEAD( salary, 1, 0 ) OVER(
    PARTITION BY department
    ORDER BY salary DESCENDING
  ) AS next_salary,

  " 같은 파티션 내 이전 행의 급여 (없으면 0)
  LAG( salary, 1, 0 ) OVER(
    PARTITION BY department
    ORDER BY salary DESCENDING
  ) AS prev_salary,

  " 부서별 평균 급여 (행 축소 없이 모든 행에 표시)
  AVG( salary ) OVER(
    PARTITION BY department
  ) AS dept_avg_salary,

  " 부서별 급여 합계
  SUM( salary ) OVER(
    PARTITION BY department
  ) AS dept_total_salary

INTO TABLE @DATA(lt_emp_analysis).

" 결과: 각 직원 행에 순위, 전후 급여, 부서 평균이 함께 표시됨
cl_demo_output=>display( lt_emp_analysis ).

포인트: LEADLAG의 두 번째 인자는 오프셋(기본 1), 세 번째 인자는 범위 밖일 때의 디폴트 값입니다. 집계 윈도우(AVG, SUM)에 PARTITION BY만 지정하고 ORDER BY를 생략하면 파티션 전체를 대상으로 연산합니다.

3단계: 프로덕션 — CTE + Window 결합, NTILE, FIRST_VALUE/LAST_VALUE, 계층 쿼리

CTE로 데이터를 준비한 뒤 Window Function으로 분석하는 결합 패턴과, 계층 쿼리 기본 구조를 보여줍니다.

" ABAP 7.54+: CTE + Window Function 결합 — 고객 매출 분석
WITH
  +cte_monthly AS (
    SELECT FROM vbrk AS h
      INNER JOIN vbrp AS i ON i~vbeln = h~vbeln
    FIELDS h~kunag          AS customer,
           LEFT( h~fkdat, 6 ) AS yearmonth,
           SUM( i~netwr )   AS monthly_revenue
    WHERE h~fkdat >= '20260101'
    GROUP BY h~kunag, LEFT( h~fkdat, 6 )
  )
  SELECT FROM +cte_monthly
  FIELDS
    customer,
    yearmonth,
    monthly_revenue,

    " 고객별 매출 5분위 그룹 (1=최저, 5=최고)
    NTILE( 5 ) OVER(
      PARTITION BY customer
      ORDER BY monthly_revenue
    ) AS revenue_quintile,

    " 고객별 첫 달(최소 매출 월) 매출액
    FIRST_VALUE( monthly_revenue ) OVER(
      PARTITION BY customer
      ORDER BY yearmonth
      ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS first_month_rev,

    " 고객별 최근 달(최대 매출 월) 매출액
    LAST_VALUE( monthly_revenue ) OVER(
      PARTITION BY customer
      ORDER BY yearmonth
      ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS latest_month_rev,

    " 전월 대비 매출 변동 (현재 - 이전 월)
    monthly_revenue - LAG( monthly_revenue, 1, 0 ) OVER(
      PARTITION BY customer
      ORDER BY yearmonth
    ) AS mom_change

  INTO TABLE @DATA(lt_analysis).

cl_demo_output=>display( lt_analysis ).

LAST_VALUE 주의: LAST_VALUE를 사용할 때 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING을 명시하지 않으면, 기본 프레임이 현재 행까지만 적용되어 기대와 다른 결과가 나올 수 있습니다.

" ABAP 7.54+: WITH HIERARCHY 기본 구조 — 조직 계층 탐색
WITH
  +org_source AS (
    SELECT FROM zorg_unit
    FIELDS org_id, parent_id, org_name, manager
  ),
  +org_hierarchy AS (
    HIERARCHY(
      SOURCE +org_source
      CHILD TO PARENT ASSOCIATION _relat
      START WHERE parent_id IS INITIAL
      SIBLINGS ORDER BY org_name
    )
  )
  SELECT FROM +org_hierarchy
  FIELDS org_id,
         parent_id,
         org_name,
         manager,
         hierarchy_rank,
         hierarchy_tree_size,
         hierarchy_level
  INTO TABLE @DATA(lt_org_tree).

cl_demo_output=>display( lt_org_tree ).

성능 최적화 팁:

6. 흔한 실수 / 트러블슈팅

FAQ 1: "CTE를 사용했는데 구문 오류가 발생합니다."

시스템이 ABAP 7.51 미만일 가능성이 높습니다. sy-saprl 값을 확인하세요. 또한 CTE 이름에 + 접두사를 빠뜨리거나, 마지막 CTE 뒤에 쉼표를 넣은 경우에도 구문 오류가 발생합니다. WITH 블록의 마지막 CTE 정의와 최종 SELECT 사이에는 쉼표 없이 바로 연결되어야 합니다.

FAQ 2: "LAST_VALUE가 항상 현재 행의 값을 반환합니다."

Window Function의 기본 프레임은 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW입니다. 따라서 LAST_VALUE는 현재 행까지만 보고 "마지막 값"을 반환하게 됩니다. 파티션 전체의 마지막 값을 얻으려면 반드시 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING을 명시해야 합니다.

FAQ 3: "LEAD/LAG를 사용했더니 덤프가 발생합니다."

LEAD와 LAG는 ABAP 7.54 이상에서만 지원됩니다. 7.51~7.53 시스템에서는 사용할 수 없으며, ROW_NUMBER 기반의 셀프 조인으로 우회해야 합니다. 또한 LEAD/LAG의 디폴트 값 타입이 대상 컬럼과 일치하는지 확인하세요.

FAQ 4: "CTE를 10개 이상 체이닝하니 응답이 매우 느립니다."

CTE 체인이 길어지면 데이터베이스 옵티마이저가 최적 실행 계획을 수립하기 어려워질 수 있습니다. 일반적으로 8~10개 이내로 유지하고, 재사용성이 높은 중간 결과는 CDS View로 분리하는 것을 권장합니다. SQL Monitor로 병목 지점을 식별한 뒤 분할하세요.

FAQ 5: "SAP HANA에서 재귀 CTE를 쓰고 싶습니다."

SAP HANA의 WITH 절은 재귀 쿼리를 지원하지 않습니다. 계층적 데이터 탐색이 필요한 경우, ABAP SQL의 WITH HIERARCHY 구문을 사용하거나 HANA Calculation View의 계층 노드를 활용하는 것이 일반적인 대안입니다.

7. 다음 단계 / 관련 주제

이 가이드에서 다룬 CTE, Window Function, 계층 쿼리를 숙지했다면 다음 주제로 확장해 보세요.

8. 참고 자료


📌 본 게시물은 AI(Claude)가 공개된 자료를 기반으로 자동 생성한 콘텐츠입니다. 기술 내용의 정확성은 SAP 공식 문서 와 교차 확인하시기 바랍니다.

™ SAP, S/4HANA, ABAP, Fiori, SAP BTP 등은 SAP SE 또는 그 계열사의 등록 상표입니다. 본 사이트는 SAP SE 와 공식적인 관련이 없는 비공식 학습 자료 입니다.

📧 저작권 침해 / 오류 / 콘텐츠 신고: btpstacks.com 의 "문의" 메뉴를 이용해주세요.