부서 합계와 각 사원의 급여를 한 화면에 놓으려면 GROUP BY 로는 부족하다. GROUP BY 는 부서마다 한 행만 남기기 때문이다. 집계 함수에 OVER 를 붙이면 행은 그대로 두고 집계 값만 옆에 붙는다.
SELECT DEPTNO, ENAME, SAL, SUM(SAL) OVER (PARTITION BY DEPTNO) AS DEPT_SUM, ROUND(AVG(SAL) OVER (PARTITION BY DEPTNO)) AS DEPT_AVG, COUNT(*) OVER () AS CNT FROM EMP WHERE DEPTNO IN (10, 20) ORDER BY DEPTNO, ENAME;
DEPTNO ENAME SAL DEPT_SUM DEPT_AVG CNT---------- ---------- ---------- ---------- ---------- ---------- 10 CLARK 2450 8750 2917 8 10 KING 5000 8750 2917 8 10 MILLER 1300 8750 2917 8 20 ADAMS 1100 10875 2175 8 20 FORD 3000 10875 2175 8 20 JONES 2975 10875 2175 8 20 SCOTT 3000 10875 2175 8 20 SMITH 800 10875 2175 8
OVER () 는 WHERE 를 통과한 행 전체를 한 파티션으로 본다. CNT 가 14 가 아니라 8 인 이유다.
SUM·AVG·COUNT·MAX·MIN 모두 같은 방식으로 쓴다. 순위 함수와 달리 OVER 안을 비워도 된다.
붙인 값으로 바로 계산할 수 있다. SAL - AVG(SAL) OVER (PARTITION BY DEPTNO) 는 부서 평균과의 차이다.
30번 평균은 1566.66… 이라 ALLEN(1600)의 차이는 33, JAMES(950)는 −617 이다. W5 에서는 부서 평균을 상관 서브쿼리로 구했다. 윈도우 함수는 테이블을 한 번만 읽고 같은 결과를 낸다.
이 집계형도 WHERE 에는 쓸 수 없다. 다만 오류 번호가 순위 함수와 다르다.
SELECT ENAME, SAL FROM EMP WHERE SAL > AVG(SAL) OVER ();
SELECT ENAME, SAL FROM EMP WHERE SAL > AVG(SAL) OVER () *ERROR at line 1:ORA-00934: group function is not allowed here
실습 컨테이너(23ai)는 집계 함수 이름에서 먼저 걸려 ORA-00934 를 냈다. 어느 쪽이든 WHERE 에 쓸 수 없다는 결론은 같다. 인라인 뷰로 감싸 거른다.
위 예제는 ORDER BY SAL, ENAME 으로 동점을 갈랐다. ORDER BY SAL 만 쓰면 MARTIN·WARD(1250)의 누적 합이 같아진다.
SELECT ENAME, SAL, SUM(SAL) OVER (ORDER BY SAL) AS RANGE_SUM, SUM(SAL) OVER (ORDER BY SAL, ENAME ROWS UNBOUNDED PRECEDING) AS ROWS_SUM FROM EMP WHERE DEPTNO = 30 ORDER BY SAL, ENAME;
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING 은 바로 앞 행·현재 행·바로 뒤 행, 최대 세 행이다.
SELECT ENAME, SAL, AVG(SAL) OVER (ORDER BY SAL, ENAME ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS MOV_AVG, SUM(SAL) OVER (ORDER BY SAL, ENAME ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS MOV_SUM FROM EMP WHERE DEPTNO = 30 ORDER BY SAL, ENAME;
RANGE BETWEEN 150 PRECEDING AND 150 FOLLOWING 은 "현재 급여 ±150 안에 드는 행"이다. 행 개수와 관계없다.
SELECT ENAME, SAL, COUNT(*) OVER (ORDER BY SAL RANGE BETWEEN 150 PRECEDING AND 150 FOLLOWING) AS NEAR_CNT FROM EMP WHERE DEPTNO = 30 ORDER BY SAL, ENAME;
LAG(열) 은 정렬 순서상 바로 앞 행, LEAD(열) 은 바로 뒤 행의 값을 가져온다. 30번 부서를 입사일 순으로 본다.
SELECT ENAME, HIREDATE, SAL, LAG(SAL) OVER (ORDER BY HIREDATE) AS PREV_SAL, LEAD(SAL) OVER (ORDER BY HIREDATE) AS NEXT_SAL FROM EMP WHERE DEPTNO = 30 ORDER BY HIREDATE;
첫 행은 앞 행이 없어 PREV_SAL 이 NULL, 마지막 행은 NEXT_SAL 이 NULL 이다.
문법은 LAG(열, 오프셋, 기본값) 이다. 오프셋 기본은 1, 기본값을 생략하면 NULL 이다.
SELECT ENAME, HIREDATE, SAL, LAG(SAL, 2) OVER (ORDER BY HIREDATE) AS LAG2, LAG(SAL, 2, 0) OVER (ORDER BY HIREDATE) AS LAG2_0, LEAD(ENAME, 1, 'NONE') OVER (ORDER BY HIREDATE) AS NEXT_EMP FROM EMP WHERE DEPTNO = 30 ORDER BY HIREDATE;
세 번째 인자 0 을 주면 NULL 대신 0 이 나온다. NVL(LAG(SAL, 2), 0) 과 같은 결과다.
PARTITION BY 를 주면 파티션 경계를 넘지 않는다. 부서가 바뀌는 첫 행의 LAG 는 다시 NULL 이다. 입사일 순 LAG(ENAME) OVER (PARTITION BY DEPTNO ORDER BY HIREDATE) 에서 10번 첫 입사자 CLARK 과 20번 첫 입사자 SMITH 는 NULL 을 받는다.
LAG·LEAD 는 ORDER BY 가 필수이고 윈도우 절을 받지 않는다. LAG(SAL) OVER (ORDER BY SAL ROWS 1 PRECEDING) 은 ORA-03097 이다.
FIRST_VALUE 는 창의 첫 행, LAST_VALUE 는 창의 마지막 행 값이다. 창의 범위가 결과를 정한다.
SELECT ENAME, SAL, FIRST_VALUE(ENAME) OVER (ORDER BY SAL DESC, ENAME) AS TOP_EMP, LAST_VALUE(ENAME) OVER (ORDER BY SAL DESC, ENAME) AS LAST_DEF, LAST_VALUE(ENAME) OVER (ORDER BY SAL DESC, ENAME ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS LAST_FULL FROM EMP WHERE DEPTNO = 30 ORDER BY SAL DESC, ENAME;
ENAME SAL TOP_EMP LAST_DEF LAST_FULL---------- ---------- ---------- ---------- ----------BLAKE 2850 BLAKE BLAKE JAMESALLEN 1600 BLAKE ALLEN JAMESTURNER 1500 BLAKE TURNER JAMESMARTIN 1250 BLAKE MARTIN JAMESWARD 1250 BLAKE WARD JAMESJAMES 950 BLAKE JAMES JAMES
FIRST_VALUE 는 기본 범위(처음 ~ 현재 행)로도 첫 행이 늘 BLAKE 라 문제가 없다.
LAST_VALUE 는 기본 범위의 끝이 현재 행이다. 그래서 LAST_DEF 는 자기 이름이 나온다.
파티션의 마지막 값(최저 급여자 JAMES)을 원하면 끝을 UNBOUNDED FOLLOWING 으로 넓힌다. LAST_FULL 이 그 결과다.
SELECT ENAME, SAL, ROUND(RATIO_TO_REPORT(SAL) OVER (), 3) AS RATIO, ROUND(SAL / SUM(SAL) OVER (), 3) AS SAME FROM EMP WHERE DEPTNO = 30 ORDER BY SAL DESC, ENAME;
RATIO_TO_REPORT 는 SAL / SUM(SAL) OVER () 와 같다. OVER 안에 ORDER BY 를 넣으면 ORA-30487 이다.
SELECT ENAME, SAL, RANK() OVER (ORDER BY SAL) AS RNK, PERCENT_RANK() OVER (ORDER BY SAL) AS PR, ROUND(CUME_DIST() OVER (ORDER BY SAL), 4) AS CD FROM EMP WHERE DEPTNO = 30 ORDER BY SAL, ENAME;
집계 윈도우 함수 : SUM·AVG·COUNT·MAX·MIN 에 OVER 를 붙인 형태. 행을 줄이지 않고 집계 값을 붙인다.
윈도우 절 : ROWS|RANGE BETWEEN … AND …. 현재 행 기준으로 계산에 넣을 행의 범위를 정한다.
ROWS : 물리적인 행 개수로 범위를 잡는다. 동점도 한 행씩 따로 센다.
RANGE : 정렬 값의 범위로 창을 잡는다. 같은 값의 행은 한꺼번에 들어간다.
누적 합 : 첫 행부터 현재 행까지의 합. OVER 에 ORDER BY 만 주면 기본으로 이 모양이 된다.
LAG / LEAD : 정렬 순서상 앞 / 뒤 n 번째 행의 값을 가져온다. 없으면 기본값(생략 시 NULL).
CUME_DIST : 현재 값 이하인 행의 비율. 누적 분포라 부른다.
NTILE : 정렬된 행을 n 개 묶음으로 나눠 묶음 번호를 붙인다. 나머지는 앞 묶음부터 하나씩 더 받는다.
📝 연습 문제
선택지를 클릭하면 정답·해설이 펼쳐집니다.
문제 1
30번 부서의 급여는 950(JAMES), 1250(MARTIN), 1250(WARD), 1500(TURNER), 1600(ALLEN), 2850(BLAKE)이다. SELECT ENAME, SUM(SAL) OVER (ORDER BY SAL) AS S FROM EMP WHERE DEPTNO = 30; 에서 MARTIN 의 S 값은?
문제 2
30번 부서를 입사일 순으로 보면 ALLEN 이 가장 먼저 입사했다. SELECT ENAME, LAG(SAL, 1, 0) OVER (ORDER BY HIREDATE) AS P FROM EMP WHERE DEPTNO = 30; 에서 ALLEN 의 P 는?
문제 3
SELECT ENAME, LAST_VALUE(ENAME) OVER (ORDER BY SAL DESC, ENAME) AS LV FROM EMP WHERE DEPTNO = 30; 에서 ALLEN 행의 LV 는? (급여 내림차순: BLAKE, ALLEN, TURNER, MARTIN, WARD, JAMES)
문제 4
14행에 NTILE(4) OVER (ORDER BY SAL DESC, ENAME) 을 적용했을 때 1~4번 묶음의 행 수는 차례로?
문제 5
30번 부서 6명에게 PERCENT_RANK() OVER (ORDER BY SAL) 을 적용했다. TURNER(1500)는 RANK 기준 4위다. TURNER 의 값은?