분석 SQL — 윈도우 함수 · 누계 · 기간 비교 · 소계 · 피벗
순위, 누계와 이동 평균, 전월·전년 비교, ROLLUP 소계, 조건부 집계 피벗까지 SAP 리포트의 분석 문법을 익힙니다.
1. 순위 매기기 — ROW_NUMBER · RANK · DENSE_RANK
윈도우 함수는 행을 줄이지 않고 각 행 옆에 계산 결과를 붙입니다.
SELECT KUNNR, SUM(NETWR) AS SALES,
RANK() OVER (ORDER BY SUM(NETWR) DESC) AS SALES_RANK
FROM VBAK
GROUP BY KUNNR;
| 함수 | 같은 값(동점)일 때 |
|---|---|
ROW_NUMBER() | 동점이어도 1, 2, 3 처럼 모두 다른 번호 |
RANK() | 동점은 같은 순위, 다음 순위는 건너뜀 (1, 1, 3) |
DENSE_RANK() | 동점은 같은 순위, 건너뛰지 않음 (1, 1, 2) |
NTILE(n) | 정렬 순서대로 n 개 묶음으로 나눔 |
PARTITION BY는 묶음마다 순위를 다시 매깁니다. 없으면 전체가 한 묶음입니다.- 그룹별 상위 N 은 윈도우 함수를 파생 테이블에 넣고 바깥에서 번호를 거릅니다.
WHERE에 윈도우 함수를 바로 쓸 수 없습니다. - 동점 순서가 결과에 영향을 주면 ORDER BY 에 행 번호 같은 타이브레이커를 추가하세요.
문제
2. 누계와 이동 평균 — SUM() OVER
집계 함수에 OVER (ORDER BY …) 를 붙이면 누계·이동 집계가 됩니다.
SELECT TO_VARCHAR(FKDAT, 'YYYY-MM') AS YM,
SUM(NETWR) AS SALES,
SUM(SUM(NETWR)) OVER (ORDER BY TO_VARCHAR(FKDAT, 'YYYY-MM')) AS RUNNING_TOTAL,
AVG(SUM(NETWR)) OVER (ORDER BY TO_VARCHAR(FKDAT, 'YYYY-MM')
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS MOVING_AVG_3
FROM VBRK
WHERE FKDAT BETWEEN '20250101' AND '20251231'
GROUP BY TO_VARCHAR(FKDAT, 'YYYY-MM');
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW는 "현재 행과 앞의 두 행", 곧 3행 이동 평균입니다.- 기본 프레임은 ORDER BY 값이 같은 행을 함께 묶습니다. 월 단위처럼 동점이 없으면 문제가 없지만, 명시하는 편이 안전합니다.
- 누계는 월별로 먼저 집계한 뒤 그 위에 윈도우 함수를 적용합니다. 원본 행에 바로 적용하면 문서 건수만큼 누적됩니다.
- 연간 매출의 절반이 쌓인 달처럼 "누계가 기준을 넘는 첫 행"은 바깥 쿼리에서 조건을 겁니다.
문제
3. 기간 비교 — LAG · LEAD
LAG(값, n) 은 정렬 순서상 n 행 앞의 값을, LEAD(값, n) 은 n 행 뒤의 값을 가져옵니다(n 을 빼면 1).
SELECT YM, SALES,
LAG(SALES) OVER (ORDER BY YM) AS PREV_SALES,
ROUND((SALES - LAG(SALES) OVER (ORDER BY YM)) * 100
/ NULLIF(LAG(SALES) OVER (ORDER BY YM), 0), 2) AS GROWTH_PCT
FROM (
SELECT TO_VARCHAR(FKDAT, 'YYYY-MM') AS YM, SUM(NETWR) AS SALES
FROM VBRK
WHERE FKDAT BETWEEN '20250101' AND '20251231'
GROUP BY TO_VARCHAR(FKDAT, 'YYYY-MM')
);
- LAG 은 행 기준입니다. 중간 달이 없으면 전월이 아니라 그 전 데이터 행과 비교하게 됩니다. 월 목록을 먼저 채운 뒤 비교하세요.
- 전년 동월 비교는 12행 전 값을 쓰거나 연도·월 키로 셀프 조인합니다.
- 증감률은 분모가 0 이 될 수 있으니
NULLIF/NDIV0로 처리합니다. - 첫 행은 이전 값이 없어 NULL 입니다. 기본값이 필요하면
LAG(값, 1, 0).
문제
4. 소계와 합계 — ROLLUP · GROUPING SETS
GROUP BY ROLLUP(a, b) 는 (a, b) 조합, a 소계, 전체 합계를 한 번에 냅니다. GROUPING SETS 는 필요한 조합을 직접 고릅니다.
SELECT WERKS, MATKL,
GROUPING(WERKS) AS IS_TOTAL,
SUM(NETWR) AS SALES
FROM VBRP
GROUP BY ROLLUP(WERKS, MATKL);
- 소계·합계 행은 묶인 컬럼이 NULL 로 나옵니다.
GROUPING(컬럼)이 1 이면 그 컬럼이 집계 때문에 NULL 인 것입니다. - 실제 NULL 값과 소계 행을 구분하려면 반드시 GROUPING() 을 쓰세요.
IFNULL로 먼저 바꾸면 구분할 수 없습니다. - 소계 행을 빼고 상세만 보고 싶으면
HAVING GROUPING(컬럼) = 0을 겁니다. - 화면 표시용 라벨은
CASE WHEN GROUPING(WERKS) = 1 THEN '전체' ELSE WERKS END처럼 만듭니다.
문제
5. 행을 열로 — 조건부 집계 피벗
SUM(CASE WHEN … THEN 금액 ELSE 0 END) 로 범주별 값을 각각의 열로 만듭니다.
SELECT KUNNR,
SUM(CASE WHEN QUARTER(FKDAT) = 1 THEN NETWR ELSE 0 END) AS Q1,
SUM(CASE WHEN QUARTER(FKDAT) = 2 THEN NETWR ELSE 0 END) AS Q2,
SUM(NETWR) AS TOTAL,
ROUND(SUM(CASE WHEN QUARTER(FKDAT) = 1 THEN NETWR ELSE 0 END) * 100
/ NULLIF(SUM(NETWR), 0), 2) AS Q1_PCT
FROM VBRK
WHERE FKDAT BETWEEN '20250101' AND '20251231'
GROUP BY KUNNR;
- 열의 범주를 고정해야 합니다. 데이터에 없는 범주도 열로 만들려면 CASE 에
ELSE 0을 둡니다. - 전체 대비 비중은 같은 GROUP BY 안에서
SUM(SUM(금액)) OVER ()또는 전체 합계로 나눠 구합니다. - HANA 의
MAP는 단순 코드 치환에 유용하지만, 집계 피벗은 CASE 가 분명합니다. - 분모가 0 인 그룹은 비중이 NULL 이 됩니다. 0 으로 보여야 하는지 정하세요.