1. 개요
“부서별 순위를 매기고 싶은데 각 사원 행은 그대로 남기고 싶다” 는 요구를 GROUP BY 로 처리하려다 막힌 적이 있을 것이다. GROUP BY 는 여러 행을 그룹당 한 행으로 줄여버려서 개별 사원 정보가 사라진다. 이럴 때 쓰는 게 윈도우 함수다.
2. 핵심 내용
2-1. GROUP BY 와 무엇이 다른가
GROUP BY 는 행을 그룹으로 묶고 집계해서 한 행으로 압축한다.
SELECT department, AVG(salary)
FROM employees
GROUP BY department;
-- 부서 수만큼만 행이 나온다윈도우 함수는 행을 그룹으로 나누는 것까지는 같지만(PARTITION BY), 압축하지 않고 원래 행 수를 그대로 유지하면서 각 행에 그룹 기준의 계산 결과를 붙여준다.
SELECT name, department, salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg
FROM employees;
-- 사원 수만큼 행이 그대로 나오고, 각 행에 자기 부서 평균이 붙는다PARTITION BY 는 GROUP BY 와 비슷하게 그룹을 나누는 역할이고, ORDER BY 는 그 그룹 안에서 순서를 정하는 역할이다.
2-2. RANK, DENSE_RANK, ROW_NUMBER
셋 다 순위를 매기지만 동점 처리 방식이 다르다.
SELECT name, department, salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dense_rnk,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_num
FROM employees;같은 부서에 연봉이 같은 사원이 둘 있다고 하면(공동 1등):
RANK(): 1, 1, 3 — 동점자만큼 다음 순위를 건너뛴다.DENSE_RANK(): 1, 1, 2 — 순위를 건너뛰지 않는다.ROW_NUMBER(): 1, 2, 3 — 동점이어도 무조건 서로 다른 번호를 매긴다. “부서별 연봉 1등 한 명만 뽑기” 처럼 동점자 중 아무나 하나만 골라야 할 때 유용하다.
2-3. 누적합과 이동 평균
ORDER BY 와 프레임(ROWS BETWEEN ...)을 조합하면 누적합이나 이동 평균도 쉽게 만든다.
SELECT order_date, amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM orders;UNBOUNDED PRECEDING AND CURRENT ROW 는 “맨 처음 행부터 현재 행까지” 라는 뜻이라, 날짜순으로 하나씩 더해가는 누적 매출이 그대로 나온다. 같은 방식으로 ROWS BETWEEN 6 PRECEDING AND CURRENT ROW 를 쓰면 최근 7일 이동 평균도 만들 수 있다. 예전 같으면 이런 계산을 애플리케이션이 전체 행을 읽어와서 루프를 돌리며 계산했는데, 윈도우 함수를 쓰면 DB 안에서 한 번에 끝난다.
3. 마무리
요약
- GROUP BY 는 그룹당 한 행으로 압축하지만, 윈도우 함수는 원래 행 수를 유지한 채 계산 결과를 각 행에 붙인다.
- PARTITION BY 는 그룹을 나누고, ORDER BY 는 그 안에서 순서를 정한다.
- RANK 는 동점자만큼 순위를 건너뛰고, DENSE_RANK 는 안 건너뛰고, ROW_NUMBER 는 동점이어도 무조건 번호가 다르다.
- 프레임(ROWS BETWEEN)을 조합하면 누적합, 이동 평균도 DB 안에서 바로 계산된다.
다음은 N+1 문제는 SQL 문제가 아니다 이다.