본문으로 건너뛰기

윈도우 함수

SQLD 목차SQL 활용레슨 27 / 35

목차

윈도우 함수(window function)는 행을 줄이지 않고, 행마다 '창'에 든 행들로 계산한 합계·순위·앞뒤 값을 붙입니다. 창은 OVER 절이 정합니다.

GROUP BY는 그룹을 한 줄로 접지만, 윈도우 함수는 줄을 그대로 두고 순위나 누적 합을 줄마다 붙입니다. Oracle에서는 분석 함수(analytic function)라고 부르며, 어느 이름으로 나와도 같은 것입니다. 그림의 GRADE는 SALGRADE의 급여 등급입니다. 최유나(450)와 강도윤(470)은 둘 다 4등급입니다.

7단계 중 1단계. 윈도우 함수는 행을 줄이지 않고, 창에 든 행들로 계산한 값을 행마다 붙입니다.

1OVER와 파티션

집계 함수에 OVER를 붙이면 윈도우 함수가 됩니다. 꼴은 다음과 같고 괄호 안은 모두 빼도 됩니다. 순위를 매기는 함수와 LAG·LEAD는 ORDER BY가 있어야 합니다.

함수(인자) OVER (
  PARTITION BY …
  ORDER BY …
  ROWS 또는 RANGE …
)

PARTITION BY는 행을 칸, 곧 파티션(partition)으로 나눕니다. NULL끼리도 한 칸이 되고, 빼면 결과 전체가 한 칸입니다.

OVER 안창SUM(SAL)이면
비어 있음칸 전체모든 줄에 2530
ORDER BY SAL첫 행부터 현재 행까지누적 합 300, 680, …
PARTITION BY DEPTNO부서 칸 전체20번 세 줄에 1260
둘 다칸의 첫 행부터 현재 행까지20번 380, 790, 1260

2창의 범위 — ROWS와 RANGE

범위는 ROWS나 RANGE 뒤에 BETWEEN 시작 AND 끝으로 적고, OVER에 ORDER BY가 있어야 합니다.

범위 말ROWS에서RANGE에서
UNBOUNDED PRECEDING칸의 첫 행칸의 첫 행
n PRECEDING · n FOLLOWINGn행 앞 · 뒤정렬 값 − n · + n(오름차순)
CURRENT ROW현재 행현재 행과 동점 행
UNBOUNDED FOLLOWING칸의 끝 행칸의 끝 행

ROWS UNBOUNDED PRECEDING⁠처럼 시작만 적으면 끝은 현재 행입니다.

OVER에 ORDER BY만 쓰고 범위를 적지 않으면 기본 범위는 다음과 같습니다.

RANGE BETWEEN
  UNBOUNDED PRECEDING
  AND CURRENT ROW

RANGE는 정렬 값으로 창을 정해서, 정렬 값이 같은 행은 한꺼번에 창에 들어갑니다. GRADE로 정렬하면 4등급 동점인 최유나와 강도윤이 함께 들어가 두 줄 모두 2010입니다.

3순위 함수

순위 함수(ranking function)는 OVER의 ORDER BY 차례로 번호를 매깁니다.

함수동점이 있으면예
RANK같은 순위, 다음은 건너뜀1 · 2 · 2 · 4
DENSE_RANK같은 순위, 건너뛰지 않음1 · 2 · 2 · 3
ROW_NUMBER늘 다른 번호1 · 2 · 3 · 4

동점에서 누가 ROW_NUMBER의 앞 번호를 받을지는 정해지지 않아서 OVER의 ORDER BY에 열을 더해 정합니다.

ROW_NUMBER() OVER (
  ORDER BY S.GRADE DESC,
    E.SAL DESC
)

윈도우 함수는 WHERE·GROUP BY·HAVING이 끝난 뒤에 계산되어 SELECT 절과 ORDER BY 절에만 씁니다.

4앞뒤 행과 비율 함수

LAG·LEAD·FIRST_VALUE·LAST_VALUE는 행 순서 함수, NTILE·RATIO_TO_REPORT·PERCENT_RANK·CUME_DIST는 비율 함수라고 부릅니다.

함수하는 일SAL 순 6행에서
LAG · LEAD한 행 앞 · 뒤의 값없으면 NULL
FIRST_VALUE · LAST_VALUE창의 첫 값 · 끝 값300 · 현재 행까지의 끝 값
NTILE(4)네 묶음 번호2 · 2 · 1 · 1행
RATIO_TO_REPORT합계에서의 비율520 ÷ 2530 ≈ 0.21
PERCENT_RANK(순위 − 1) ÷ (행 수 − 1)0 · 0.2 · … · 1
CUME_DIST이하인 행 수 ÷ 행 수1/6 · … · 6/6

LAG(SAL, 2, 0)은 두 행 앞의 값이고, 앞 행이 없는 첫 두 행은 0입니다. 기본 범위의 창은 현재 행에서 끝나서 LAST_VALUE는 동점이 없으면 현재 행의 값입니다. 동점이 있으면 그 무리의 마지막 행 값이라 정해지지 않습니다. 칸의 끝값을 원하면 범위를 다음처럼 넓힙니다.

ROWS BETWEEN
  UNBOUNDED PRECEDING
  AND UNBOUNDED FOLLOWING

예제

RANGE와 ROWS를 견줍니다. ROWS_SUM은 동점끼리 차례를 정하려고 ORDER BY에 E.SAL을 더했습니다.

SELECT E.ENAME,
  E.SAL,
  S.GRADE,
  SUM(E.SAL) OVER (
    ORDER BY S.GRADE
  ) AS RANGE_SUM,
  SUM(E.SAL) OVER (
    ORDER BY S.GRADE, E.SAL
    ROWS UNBOUNDED PRECEDING
  ) AS ROWS_SUM
FROM EMP E
  JOIN SALGRADE S
  ON E.SAL BETWEEN
    S.LOSAL AND S.HISAL
ORDER BY E.SAL;
ENAMESALGRADERANGE_SUMROWS_SUM
정하늘3001300300
이서연3802680680
박지호410310901090
최유나450420101540
강도윤470420102010
김민준520525302530

최유나 줄에서 RANGE는 같은 4등급인 강도윤까지 창에 넣어 2010이고, ROWS는 최유나까지만 더해 1540입니다.

시험 포인트

  • GROUP BY는 행을 줄이고, 윈도우 함수는 행 수를 그대로 둡니다.
  • OVER에 ORDER BY만 있으면 기본 범위가 RANGE라 동점 행은 같은 누적 합을 받습니다.
  • 동점 다음 번호는 RANK만 건너뛰고, ROW_NUMBER는 동점에도 다른 번호를 줍니다.
  • 기본 범위의 LAST_VALUE는 칸의 끝값이 아니라 현재 행까지의 마지막 값입니다.
  • 윈도우 함수는 WHERE에 쓸 수 없습니다.

확인 문제

1. 다음 쿼리에서 박지호 줄의 CNT와 TOP_SAL은 각각 얼마일까요?

SELECT ENAME,
  SAL,
  COUNT(*) OVER (
    PARTITION BY DEPTNO
  ) AS CNT,
  MAX(SAL) OVER (
    PARTITION BY DEPTNO
    ORDER BY SAL
  ) AS TOP_SAL
FROM EMP
WHERE DEPTNO = 20
ORDER BY SAL;
정답

CNT는 3, TOP_SAL은 410입니다.

ENAMESALCNTTOP_SAL
이서연3803380
박지호4103410
강도윤4703470
  • CNT는 OVER에 ORDER BY가 없어 창이 20번 칸 전체라 3입니다.
  • TOP_SAL은 창이 칸의 첫 행부터 현재 행까지라 380·410 가운데 큰 410이고, 칸 전체 최댓값 470이 아닙니다.

2. MGR 순으로 순위를 매겼습니다. MGR이 NULL인 김민준 줄의 RNK와 DRNK는 얼마일까요?

SELECT ENAME,
  MGR,
  RANK() OVER (
    ORDER BY MGR
  ) AS RNK,
  DENSE_RANK() OVER (
    ORDER BY MGR
  ) AS DRNK
FROM EMP
ORDER BY RNK, EMPNO;
정답

RNK는 6, DRNK는 4입니다.

ENAMEMGRRNKDRNK
최유나100111
강도윤100111
정하늘100432
이서연100643
박지호100643
김민준NULL64
  • Oracle은 오름차순에서 NULL을 맨 뒤에 두므로 김민준이 마지막입니다.
  • 1001과 1006이 두 명씩 동점이라 RANK는 건너뛰어 6, DENSE_RANK는 건너뛰지 않아 4입니다.

3. 월급 상위 3명을 거르다가 오류가 났습니다. 까닭과 고칠 방법은 무엇일까요?

SELECT ENAME
FROM EMP
WHERE RANK() OVER (
  ORDER BY SAL DESC
) <= 3;
정답

순위는 WHERE가 행을 거른 뒤에 매겨져서 WHERE에 쓸 수 없습니다(ORA-30483). 인라인 뷰에서 순위를 붙인 뒤 메인 쿼리의 WHERE에서 거릅니다(→ Top N 쿼리 레슨).

SELECT ENAME,
  SAL,
  RNK
FROM (
  SELECT ENAME,
    SAL,
    RANK() OVER (
      ORDER BY SAL DESC
    ) AS RNK
  FROM EMP
)
WHERE RNK <= 3
ORDER BY RNK;
ENAMESALRNK
김민준5201
강도윤4702
최유나4503

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

SQLD 목차로