Database/개념

[DATABASE] 데이터베이스 윈도우 함수 사용해보기

꽁담 2018. 11. 25. 23:47

윈도우 함수 개요



기존 관계형 데이터베이스는 컬럼과 컬럼간의 연산, 비교, 연결이나 집합에 대한 집계는 쉬운 반면,

행과 행간의 관계를 정의하거나, 비교, 연산하는 것을 하나의 SQL 문으로 처리하는 것은 매우 어렵습니다.


PL/SQL 같은 절차형 프로그램을 사용하거나, INLINE VIEW 를 이용해 복잡한 SQL 문을 작성해야 하던 것을 부분적이나마 행과 행간의 관계를 쉽게 정의하기 위해 만든 함수가 윈도우 함수입니다.


분석함수(ANALYTIC FUNCTION)나 순위함수(RANK FUNCTION)로도 알려져 있는 윈도우 함수는 데이터웨어하우스에서 발전한 기능입니다.

윈도우 함수는 다른 함수와는 달리 중첩해서 사용하지는 못하지만, 서브쿼리에서는 사용할 수 있습니다.



윈도우 함수의 종류 및 문법



윈도우 함수의 종류는 크게 다섯 개의 그룹으로 분류할 수 있습니다.

여기에서는 통계 분석 함수는 다루지 않습니다.


그룹 내 순위(RANK) 함수 : RANK, DSENSE_RANK, ROW_NUMBER

그룹 내 집계(AGGREGATE) 함수 : SUM, MAX, MIN, AVG, COUNT

그룹 내 행 순서 함수 : FIRST_VALUE, LAST_VALUE, LAG, LEAD

그룹 내 비율 함수 : CUME_DIST, PERCENT_RANK, NTILE, RATIO_TO_REPORT

통계 분석 함수 : CORR, COVAR_POP, STDDEV, STDDEV_POP, STDDEV_SAMP 등


윈도우 함수는 OVER 문구가 키워드로 필수 포함됩니다.

SELECT WINDOW_FUNCTION (ARGUMENTS) OVER
( [PARTITION BY 칼럼] [ORDER BY 칼럼] [WINDOWING 절] )
FROM 테이블 명;



그룹 내 순위 함수


RANK


RANK 함수는 ORDER BY 절을 포함한 QUERY 문에서 특정 항목에 대한 순위를 구하는 함수입니다.

RANK 를 사용하여 사원 테이블에서 급여가 높은 순서와 JOB 별로 급여가 높은 순서를 같이 출력하는 방법입니다.

SQL> SELECT JOB, ENAME, SAL,
  2         RANK() OVER (ORDER BY SAL DESC) ALL_RANK,
  3         RANK() OVER (PARTITION BY JOB ORDER BY SAL DESC) JOB_RANK
  4  FROM EMP;

JOB       ENAME             SAL   ALL_RANK   JOB_RANK
--------- ---------- ---------- ---------- ----------
PRESIDENT KING             5000          1          1
ANALYST   FORD             3000          2          1
ANALYST   SCOTT            3000          2          1
MANAGER   JONES            2975          4          1
MANAGER   BLAKE            2850          5          2
MANAGER   CLARK            2450          6          3
SALESMAN  ALLEN            1600          7          1
SALESMAN  TURNER           1500          8          2
CLERK     MILLER           1300          9          1
SALESMAN  WARD             1250         10          3
SALESMAN  MARTIN           1250         10          3

JOB       ENAME             SAL   ALL_RANK   JOB_RANK
--------- ---------- ---------- ---------- ----------
CLERK     ADAMS            1100         12          2
CLERK     JAMES             950         13          3
CLERK     SMITH             800         14          4

14 rows selected.


ALL_RANK 는 급여가 높은 순으로 번호를 부여합니다.

JOB_RANK 는 직업별로 묶은뒤 각 직업별로 급여가 높은 순위로 번호를 부여합니다.


DSENSE_RANK


RANK 함수와 흡사하나, 동일한 순위를 하나의 건수로 취급하는 것이 다릅니다.

이해가 되시나요? ALL_RANK 에서 2, 2 는 2개고 다음 숫자가 4 이지만 DENSE_RANK 는 2, 2 다음의 숫자가 3 입니다.

SQL> SELECT JOB, ENAME, SAL, 2 RANK() OVER (ORDER BY SAL DESC) ALL_RANK, 3 DENSE_RANK() OVER (ORDER BY SAL DESC) DENSE_RANK 4 FROM EMP; JOB ENAME SAL ALL_RANK DENSE_RANK --------- ---------- ---------- ---------- ---------- PRESIDENT KING 5000 1 1 ANALYST FORD 3000 2 2 ANALYST SCOTT 3000 2 2 MANAGER JONES 2975 4 3 MANAGER BLAKE 2850 5 4 MANAGER CLARK 2450 6 5 SALESMAN ALLEN 1600 7 6 SALESMAN TURNER 1500 8 7 CLERK MILLER 1300 9 8 SALESMAN WARD 1250 10 9 SALESMAN MARTIN 1250 10 9 JOB ENAME SAL ALL_RANK DENSE_RANK --------- ---------- ---------- ---------- ---------- CLERK ADAMS 1100 12 10 CLERK JAMES 950 13 11 CLERK SMITH 800 14 12 14 rows selected.


ROW_NUMBER


동일한 값이라도 고유한 순위를 부여합니다.

같은 값에 대해서는 어떤 순서가 정해질지 알 수 없습니다. ORACLE 의 경우에는 ROWID 가 적은 행이 먼저 나옵니다.

이 부분은 벤더별로 다른 결과가 나올 수 있기 때문에 ORDER BY 절을 이용하여 정렬 기준을 정의해야 합니다.

SQL> SELECT JOB, ENAME, SAL,
  2         RANK() OVER (ORDER BY SAL DESC) ALL_RANK,
  3         ROW_NUMBER() OVER (ORDER BY SAL DESC) ROW_NUMBER
  4  FROM EMP;

JOB       ENAME             SAL   ALL_RANK ROW_NUMBER
--------- ---------- ---------- ---------- ----------
PRESIDENT KING             5000          1          1
ANALYST   FORD             3000          2          2
ANALYST   SCOTT            3000          2          3
MANAGER   JONES            2975          4          4
MANAGER   BLAKE            2850          5          5
MANAGER   CLARK            2450          6          6
SALESMAN  ALLEN            1600          7          7
SALESMAN  TURNER           1500          8          8
CLERK     MILLER           1300          9          9
SALESMAN  WARD             1250         10         10
SALESMAN  MARTIN           1250         10         11

JOB       ENAME             SAL   ALL_RANK ROW_NUMBER
--------- ---------- ---------- ---------- ----------
CLERK     ADAMS            1100         12         12
CLERK     JAMES             950         13         13
CLERK     SMITH             800         14         14

14 rows selected.



그룹 내 집계 함수



SUM


SUM 함수를 이용하여 파티션별로 윈도우의 합을 구할 수 있습니다.

MGR_SUM 을 보면 각 MGR 별로 합산된 값을 확인할 수 있습니다.

SQL> SELECT MGR, ENAME, SAL,
  2         SUM(SAL) OVER (PARTITION BY MGR) MGR_SUM
  3  FROM EMP;

       MGR ENAME             SAL    MGR_SUM
---------- ---------- ---------- ----------
      7566 FORD             3000       6000
      7566 SCOTT            3000       6000
      7698 JAMES             950       6550
      7698 ALLEN            1600       6550
      7698 WARD             1250       6550
      7698 TURNER           1500       6550
      7698 MARTIN           1250       6550
      7782 MILLER           1300       1300
      7788 ADAMS            1100       1100
      7839 BLAKE            2850       8275
      7839 JONES            2975       8275

       MGR ENAME             SAL    MGR_SUM
---------- ---------- ---------- ----------
      7839 CLARK            2450       8275
      7902 SMITH             800        800
           KING             5000       5000

14 rows selected.


MAX


MAX 함수를 이용하여 파티션별 윈도우의 최대값을 구할 수 있습니다.

MGR_MAX 를 보면 각 MGR 별로 최대 값이 출력되는 것을 확인할 수 있습니다.

SQL> SELECT MGR, ENAME, SAL,
  2         MAX(SAL) OVER (PARTITION BY MGR) MGR_MAX
  3  FROM EMP;

       MGR ENAME             SAL    MGR_MAX
---------- ---------- ---------- ----------
      7566 FORD             3000       3000
      7566 SCOTT            3000       3000
      7698 JAMES             950       1600
      7698 ALLEN            1600       1600
      7698 WARD             1250       1600
      7698 TURNER           1500       1600
      7698 MARTIN           1250       1600
      7782 MILLER           1300       1300
      7788 ADAMS            1100       1100
      7839 BLAKE            2850       2975
      7839 JONES            2975       2975

       MGR ENAME             SAL    MGR_MAX
---------- ---------- ---------- ----------
      7839 CLARK            2450       2975
      7902 SMITH             800        800
           KING             5000       5000

14 rows selected.


MIN, AVG, COUNT 함수도 위의 방법과 동일하므로 더는 작성하지 않겠습니다.



그룹 내 행 순서



FIRST_VALUE


파티션별 윈도우에서 가장 먼저 나온 값을 구합니다.

부서별 직원들을 연봉이 높은 수서로 정렬하고, 부서별로 가장 먼저 나온 값을 출력합니다.

SQL> SELECT DEPTNO, ENAME, SAL,                                                
  2         FIRST_VALUE(ENAME) OVER (PARTITION BY DEPTNO ORDER BY SAL DESC
  3                                  ROWS UNBOUNDED PRECEDING) AS DEPT_RICH
  4  FROM EMP;

    DEPTNO ENAME             SAL DEPT_RICH
---------- ---------- ---------- ----------
        10 KING             5000 KING
        10 CLARK            2450 KING
        10 MILLER           1300 KING
        20 SCOTT            3000 SCOTT
        20 FORD             3000 SCOTT
        20 JONES            2975 SCOTT
        20 ADAMS            1100 SCOTT
        20 SMITH             800 SCOTT
        30 BLAKE            2850 BLAKE
        30 ALLEN            1600 BLAKE
        30 TURNER           1500 BLAKE

    DEPTNO ENAME             SAL DEPT_RICH
---------- ---------- ---------- ----------
        30 MARTIN           1250 BLAKE
        30 WARD             1250 BLAKE
        30 JAMES             950 BLAKE

14 rows selected

UNBOUNDED PRECEDING 은 현재 행을 기준으로 파티션 내의 첫 번째 행까지의 범위를 지정합니다.

DEPTNO 20 의 SCOTT 와 FORD 의 SAL 은 같은 값을 가집니다.

이 중 어느 사람이 최고 급여자로 선택될지는 SQL 문만 가지고는 판단할 수 없습니다.

FIRST_VALUE 는 다른 함수와 달리 공동 등수를 인정하지 않고 처음 나온 행만을 처리합니다.

위처럼 공동 등수가 있을 경우에 의도적으로 세부 항목을 정렬하려는 경우 ORDER BY 절에 컬럼을 추가해야 합니다.

SQL> SELECT DEPTNO, ENAME, SAL,                                                
  2         FIRST_VALUE(ENAME) OVER (PARTITION BY DEPTNO ORDER BY SAL DESC, ENAME ASC
  3                                  ROWS UNBOUNDED PRECEDING) AS DEPT_RICH
  4  FROM EMP;

    DEPTNO ENAME             SAL DEPT_RICH
---------- ---------- ---------- ----------
        10 KING             5000 KING
        10 CLARK            2450 KING
        10 MILLER           1300 KING
        20 FORD             3000 FORD
        20 SCOTT            3000 FORD
        20 JONES            2975 FORD
        20 ADAMS            1100 FORD
        20 SMITH             800 FORD
        30 BLAKE            2850 BLAKE
        30 ALLEN            1600 BLAKE
        30 TURNER           1500 BLAKE

    DEPTNO ENAME             SAL DEPT_RICH
---------- ---------- ---------- ----------
        30 MARTIN           1250 BLAKE
        30 WARD             1250 BLAKE
        30 JAMES             950 BLAKE

14 rows selected.


LAST_VALUE


파티션별로 윈도우에서 가장 나중에 나온 값을 구합니다.

SQL> SELECT DEPTNO, ENAME, SAL,                                                
  2         LAST_VALUE(ENAME) OVER (PARTITION BY DEPTNO ORDER BY SAL DESC
  3                                 ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS DEPT_POOR
  4  FROM EMP;

    DEPTNO ENAME             SAL DEPT_POOR
---------- ---------- ---------- ----------
        10 KING             5000 MILLER
        10 CLARK            2450 MILLER
        10 MILLER           1300 MILLER
        20 SCOTT            3000 SMITH
        20 FORD             3000 SMITH
        20 JONES            2975 SMITH
        20 ADAMS            1100 SMITH
        20 SMITH             800 SMITH
        30 BLAKE            2850 JAMES
        30 ALLEN            1600 JAMES
        30 TURNER           1500 JAMES

    DEPTNO ENAME             SAL DEPT_POOR
---------- ---------- ---------- ----------
        30 MARTIN           1250 JAMES
        30 WARD             1250 JAMES
        30 JAMES             950 JAMES

14 rows selected.

ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING 은 현재 행을 포함해서 파티션 내의 마지막 행까지의 범위를 지정합니다.

FIRST_VALUE 와 속성은 비슷하므로 세부 항목의 컬럼을 정렬해야 하는 경우 ORDER BY 를 작성합니다.


LAG


파티션별로 윈도우에서 이전 몇 번째 행의 값을 가져올 수 있습니다.

입사일자가 빠른 순으로 정렬하고, 본인보다 입사일자가 한 명 앞선 사원의 급여를 본인의 급여와 함께 출력합니다.

SQL> SELECT ENAME, HIREDATE, SAL, 
  2         LAG(SAL) OVER (ORDER BY HIREDATE) AS PERV_SAL
  3  FROM EMP
  4  WHERE JOB = 'SALESMAN';

ENAME      HIREDATE        SAL   PERV_SAL
---------- -------- ---------- ----------
ALLEN      81-02-11       1600
WARD       81-02-23       1250       1600
TURNER     81-08-21       1500       1250
MARTIN     81-09-10       1250       1500


LAG 함수는 3개의 인자를 사용할 수 있는데, 두 번째 인자는 몇 번째 앞의 행을 가져올지 결정하는 것, 세 번째 인자는 예를 들어 파티션의 첫 번째 행의 경우 가져올 데이터가 없어 NULL 값이 들어오는데 이 경우 다른 값으로 바꿔줍니다.

SQL> SELECT ENAME, HIREDATE, SAL,
  2         LAG(SAL, 2, 0) OVER (ORDER BY HIREDATE) AS PERV_SAL
  3  FROM EMP
  4  WHERE JOB = 'SALESMAN';

ENAME      HIREDATE        SAL   PERV_SAL
---------- -------- ---------- ----------
ALLEN      81-02-11       1600          0
WARD       81-02-23       1250          0
TURNER     81-08-21       1500       1600
MARTIN     81-09-10       1250       1250


LEAD


파티션별로 윈도우에서 이후 몇 번째의 행의 값을 가져올 수 있습니다.

입사일자가 빠른 순으로 정렬하고, 본인보다 입사일자가 한 명 뒤인 사원의 입사일짜를 함께 출력합니다.

SQL> SELECT ENAME, HIREDATE,
  2         LEAD(HIREDATE, 1) OVER (ORDER BY HIREDATE) AS NEXTHIRED
  3  FROM EMP;

ENAME      HIREDATE NEXTHIRE
---------- -------- --------
SMITH      80-12-09 81-02-11
ALLEN      81-02-11 81-02-23
WARD       81-02-23 81-04-01
JONES      81-04-01 81-05-01
BLAKE      81-05-01 81-05-09
CLARK      81-05-09 81-08-21
TURNER     81-08-21 81-09-10
MARTIN     81-09-10 81-11-17
KING       81-11-17 81-12-11
JAMES      81-12-11 81-12-11
FORD       81-12-11 82-01-11

ENAME      HIREDATE NEXTHIRE
---------- -------- --------
MILLER     82-01-11 82-12-22
SCOTT      82-12-22 83-01-15
ADAMS      83-01-15

14 rows selected.



그룹 내 비율 함수



RATIO_TO_REPORT


파티션 내 전체 SUM 값에 대한 행별 컬럼 값의 백분율을 소수점으로 구할 수 있습니다.

결과 값은 >0 과 <=1 사이의 범위를 가지며, 개별 RATIO 의 합을 구하면 1 이 됩니다.

SQL> SELECT ENAME, SAL,
  2         ROUND(RATIO_TO_REPORT(SAL) OVER(), 2) AS RTR
  3  FROM EMP
  4  WHERE JOB = 'SALESMAN';

ENAME             SAL        RTR
---------- ---------- ----------
MARTIN           1250        .22
ALLEN            1600        .29
TURNER           1500        .27
WARD             1250        .22


PERCENT_RANK


파티션별 윈도우에서 제일 먼저 나온 것을 0 으로 제일 늦게 나오는 것을 1 로 하여 값이 아닌 행의 순서별 백분율을 구합니다.

예를 들어서 DEPTNO 10 의 CLARK 는 DEPTNO 10 의 중간위치에 속하므로 0.5 가 출력됩니다.

SQL> SELECT DEPTNO, ENAME, SAL,
  2         PERCENT_RANK() OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) AS PR
  3  FROM EMP;

    DEPTNO ENAME             SAL         PR
---------- ---------- ---------- ----------
        10 KING             5000          0
        10 CLARK            2450         .5
        10 MILLER           1300          1
        20 SCOTT            3000          0
        20 FORD             3000          0
        20 JONES            2975         .5
        20 ADAMS            1100        .75
        20 SMITH             800          1
        30 BLAKE            2850          0
        30 ALLEN            1600         .2
        30 TURNER           1500         .4

    DEPTNO ENAME             SAL         PR
---------- ---------- ---------- ----------
        30 MARTIN           1250         .6
        30 WARD             1250         .6
        30 JAMES             950          1

14 rows selected.


CUME_DIST


파티션별 윈도우의 전체건수에서 현재 행보다 작거나 같은 건수에 대한 누적백분율을 구합니다.

DEPTNO 10 의 CLARK 는 DEPTNO 3건 중 중간에 위치하므로 0.666666 이 출력됩니다.

DEPTNO 20 의 SCOTT 와 FORD 는 5건중 2건이므로 0.4 가 출력됩니다.

SQL> SELECT DEPTNO, ENAME, SAL,
  2         CUME_DIST() OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) AS CD
  3  FROM EMP;

    DEPTNO ENAME             SAL         CD
---------- ---------- ---------- ----------
        10 KING             5000 .333333333
        10 CLARK            2450 .666666667
        10 MILLER           1300          1
        20 SCOTT            3000         .4
        20 FORD             3000         .4
        20 JONES            2975         .6
        20 ADAMS            1100         .8
        20 SMITH             800          1
        30 BLAKE            2850 .166666667
        30 ALLEN            1600 .333333333
        30 TURNER           1500         .5

    DEPTNO ENAME             SAL         CD
---------- ---------- ---------- ----------
        30 MARTIN           1250 .833333333
        30 WARD             1250 .833333333
        30 JAMES             950          1

14 rows selected.


NTILE


파티션별 전체 건수를 인자값으로 N 등분한 결과를 구할 수 있습니다.

SQL> SELECT DEPTNO, ENAME, SAL,
  2        NTILE(4) OVER (ORDER BY SAL DESC) AS NTILE_4
  3  FROM EMP;

    DEPTNO ENAME             SAL    NTILE_4
---------- ---------- ---------- ----------
        10 KING             5000          1
        20 SCOTT            3000          1
        20 FORD             3000          1
        20 JONES            2975          1
        30 BLAKE            2850          2
        10 CLARK            2450          2
        30 ALLEN            1600          2
        30 TURNER           1500          2
        10 MILLER           1300          3
        30 WARD             1250          3
        30 MARTIN           1250          3

    DEPTNO ENAME             SAL    NTILE_4
---------- ---------- ---------- ----------
        20 ADAMS            1100          4
        30 JAMES             950          4
        20 SMITH             800          4

14 rows selected.



이 내용은 'SQL 전문가 가이드, 한국데이터베이스 진흥원' 에서 공부한 내용입니다.