윈도우 함수란?
데이터베이스에서 복잡한 분석 쿼리를 작성해야 할 때, 여러 개의 서브쿼리나 임시 테이블을 사용해 본 경험이 있을 것입니다. 특히 각 행의 데이터와 함께 집계 결과를 동시에 보여줘야 하는 경우에는 더욱 그렇습니다. PostgreSQL의 **윈도우 함수(Window Functions)**는 이러한 복잡한 문제를 간단하게 해결해 줍니다.
윈도우 함수는 SQL:1999 표준에 도입되었으며, PostgreSQL 8.4 버전부터 지원하기 시작했습니다. 이 함수들은 행 집합("윈도우")에 대한 계산을 수행하면서도 각 행의 개별성을 유지한다는 특징이 있습니다. 일반 집계 함수가 여러 행을 하나의 행으로 축소하는 것과 달리, 윈도우 함수는 원래 행을 그대로 유지하면서 추가 계산을 수행합니다.
-- 일반 집계 함수: 여러 행이 하나로 축소됨
SELECT department, AVG(salary)
FROM employees
GROUP BY department;
-- 윈도우 함수: 각 행을 유지하면서 부서별 평균 급여도 표시
SELECT name, department, salary,
AVG(salary) OVER (PARTITION BY department) as dept_avg_salary
FROM employees;
윈도우 함수의 기본 구문
윈도우 함수의 기본 구문은 다음과 같습니다:
함수_이름() OVER (
PARTITION BY 표현식1, 표현식2, ...
ORDER BY 표현식1 [ASC|DESC], 표현식2 [ASC|DESC], ...
프레임_절
)
각 요소의 역할:
- 함수_이름(): 사용할 윈도우 함수 (예: SUM(), AVG(), ROW_NUMBER() 등)
- OVER(): 윈도우 함수임을 나타내는 키워드
- PARTITION BY: 데이터를 그룹화하는 방법 (선택 사항)
- ORDER BY: 윈도우 내에서 행의 순서 지정 (선택 사항)
- 프레임_절: 현재 행에 대한 윈도우 프레임 정의 (선택 사항, 예: ROWS BETWEEN)
순위 관련 윈도우 함수
순위 관련 함수는 윈도우 함수 중에서 가장 많이 사용되는 함수들입니다.
ROW_NUMBER()
각 행에 고유한 순차 번호를 할당합니다.
SELECT name, department, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) as overall_rank,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as dept_rank
FROM employees;
RANK()
동일한 값에 동일한 순위를 할당하고, 다음 순위는 중복된 순위 수만큼 건너뜁니다.
SELECT name, department, salary,
RANK() OVER (ORDER BY salary DESC) as rank
FROM employees;
DENSE_RANK()
동일한 값에 동일한 순위를 할당하되, 순위에 빈틈이 없게 합니다.
SELECT name, department, salary,
RANK() OVER (ORDER BY salary DESC) as rank,
DENSE_RANK() OVER (ORDER BY salary DESC) as dense_rank
FROM employees;
예를 들어, 급여가 100, 90, 90, 80인 경우:
- RANK(): 1, 2, 2, 4
- DENSE_RANK(): 1, 2, 2, 3
PERCENT_RANK()
백분율 순위를 계산합니다 (0~1 사이의 값).
SELECT name, department, salary,
PERCENT_RANK() OVER (ORDER BY salary) as percentile
FROM employees;
NTILE(n)
결과 집합을 n개의 동일한 크기의 그룹으로 나눕니다.
-- 직원들을 급여 기준으로 4개의 그룹으로 나누기
SELECT name, salary,
NTILE(4) OVER (ORDER BY salary DESC) as salary_quartile
FROM employees;
집계 윈도우 함수
일반 집계 함수(SUM, AVG, COUNT, MIN, MAX 등)도 윈도우 함수로 사용할 수 있습니다.
SELECT name, department, salary,
SUM(salary) OVER (PARTITION BY department) as dept_total_salary,
AVG(salary) OVER (PARTITION BY department) as dept_avg_salary,
COUNT(*) OVER (PARTITION BY department) as dept_employee_count,
salary / SUM(salary) OVER (PARTITION BY department) * 100 as salary_percentage
FROM employees;
누적 집계
ORDER BY를 사용하면 누적 집계를 계산할 수 있습니다.
-- 월별 누적 매출
SELECT month, revenue,
SUM(revenue) OVER (ORDER BY month) as cumulative_revenue
FROM monthly_sales;
오프셋 윈도우 함수
이전 또는 이후 행의 값에 접근할 수 있는 함수들입니다.
LAG()
현재 행보다 앞에 있는 행의 값을 반환합니다.
-- 전월 대비 매출 증가율 계산
SELECT month, revenue,
LAG(revenue) OVER (ORDER BY month) as prev_month_revenue,
(revenue - LAG(revenue) OVER (ORDER BY month)) / LAG(revenue) OVER (ORDER BY month) * 100 as growth_rate
FROM monthly_sales;
LEAD()
현재 행보다 뒤에 있는 행의 값을 반환합니다.
-- 다음 달 매출과 비교
SELECT month, revenue,
LEAD(revenue) OVER (ORDER BY month) as next_month_revenue
FROM monthly_sales;
FIRST_VALUE()와 LAST_VALUE()
윈도우 내의 첫 번째 값과 마지막 값을 반환합니다.
-- 각 부서에서 가장 높은 급여와 비교
SELECT name, department, salary,
FIRST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary DESC) as highest_salary_in_dept
FROM employees;
PARTITION BY와 ORDER BY 활용
PARTITION BY와 ORDER BY를 함께 사용하면 더 강력한 분석이 가능합니다.
-- 부서별, 연도별 누적 급여 합계
SELECT name, department, year, salary,
SUM(salary) OVER (PARTITION BY department, year ORDER BY month) as cumulative_salary_by_dept_year
FROM employee_monthly_salaries;
윈도우 프레임 (Window Frame) 지정
윈도우 프레임을 사용하면 현재 행을 기준으로 계산할 행의 범위를 더 세밀하게 지정할 수 있습니다.
-- 3개월 이동 평균 계산
SELECT month, revenue,
AVG(revenue) OVER (ORDER BY month ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as moving_avg_3months
FROM monthly_sales;
프레임 지정 옵션:
- ROWS: 물리적 행 수 기준
- RANGE: 값의 범위 기준
- BETWEEN start AND end: 시작과 끝 지점 지정
- UNBOUNDED PRECEDING: 파티션의 첫 행부터
- n PRECEDING: 현재 행에서 n행 이전까지
- CURRENT ROW: 현재 행
- n FOLLOWING: 현재 행에서 n행 이후까지
- UNBOUNDED FOLLOWING: 파티션의 마지막 행까지
실제 비즈니스 시나리오 예제
예제 1: 전자상거래 사이트의 고객 분석
-- 고객별 구매 금액 순위 및 백분위 계산
SELECT
customer_id,
order_date,
amount,
SUM(amount) OVER (PARTITION BY customer_id) as total_spent,
RANK() OVER (PARTITION BY EXTRACT(YEAR FROM order_date) ORDER BY amount DESC) as yearly_rank,
NTILE(5) OVER (ORDER BY SUM(amount) OVER (PARTITION BY customer_id) DESC) as customer_segment
FROM orders;
예제 2: 주식 시장 데이터 분석
-- 주식 가격의 이동 평균 및 전일 대비 변화율 계산
SELECT
stock_symbol,
date,
price,
AVG(price) OVER (PARTITION BY stock_symbol ORDER BY date ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) as moving_avg_10days,
(price - LAG(price) OVER (PARTITION BY stock_symbol ORDER BY date)) / LAG(price) OVER (PARTITION BY stock_symbol ORDER BY date) * 100 as daily_change_pct
FROM stock_prices;
예제 3: 매장별 판매 실적 분석
-- 매장별, 월별 판매 실적 및 전년 동월 대비 성장률
SELECT
store_id,
year,
month,
sales,
LAG(sales, 12) OVER (PARTITION BY store_id ORDER BY year, month) as sales_prev_year,
(sales - LAG(sales, 12) OVER (PARTITION BY store_id ORDER BY year, month)) / LAG(sales, 12) OVER (PARTITION BY store_id ORDER BY year, month) * 100 as yoy_growth_pct
FROM monthly_store_sales;
성능 최적화 팁
윈도우 함수는 강력하지만, 대용량 데이터셋에서는 성능 이슈가 발생할 수 있습니다. 다음은 성능 최적화를 위한 팁입니다:
- 인덱스 활용: PARTITION BY와 ORDER BY에 사용되는 컬럼에 인덱스를 생성하세요.
- 파티션 크기 제한: 가능하면 파티션 크기를 제한하여 계산량을 줄이세요.
- 불필요한 정렬 피하기: 같은 정렬 기준을 사용하는 여러 윈도우 함수가 있다면, 윈도우 별칭을 사용하세요.
-- 윈도우 별칭 사용
SELECT name, department, salary,
RANK() OVER w as rank,
DENSE_RANK() OVER w as dense_rank,
ROW_NUMBER() OVER w as row_num
FROM employees
WINDOW w AS (PARTITION BY department ORDER BY salary DESC);
- 필요한 경우에만 프레임 사용: 프레임 지정은 추가 계산을 필요로 하므로, 꼭 필요한 경우에만 사용하세요.
자주 묻는 질문
Q: 윈도우 함수와 GROUP BY의 차이점은 무엇인가요?
A: GROUP BY는 여러 행을 하나로 축소하지만, 윈도우 함수는 모든 행을 유지합니다. 윈도우 함수를 사용하면 개별 행 데이터와 집계 데이터를 동시에 볼 수 있습니다.
Q: PostgreSQL의 어떤 버전부터 윈도우 함수를 지원하나요?
A: PostgreSQL 8.4 버전부터 기본적인 윈도우 함수를 지원하기 시작했으며, 이후 버전에서 점진적으로 기능이 추가되었습니다.
Q: 윈도우 함수를 WHERE 절에서 사용할 수 있나요?
A: 아니요, 윈도우 함수는 WHERE 절에서 직접 사용할 수 없습니다. 서브쿼리나 CTE(Common Table Expression)를 사용해야 합니다.
Q: 윈도우 함수와 일반 집계 함수를 함께 사용할 수 있나요?
A: 네, 동일한 쿼리에서 GROUP BY를 사용한 일반 집계와 윈도우 함수를 함께 사용할 수 있습니다.
결론
PostgreSQL 윈도우 함수는 복잡한 분석 작업을 간단하게 수행할 수 있는 강력한 도구입니다. 순위 지정, 누적 집계, 이동 평균 등 다양한 분석 요구사항을 효율적으로 처리할 수 있으며, 서브쿼리나 임시 테이블 없이도 깔끔한 코드로 복잡한 로직을 구현할 수 있습니다.
윈도우 함수를 마스터하면 데이터 분석의 효율성과 코드 가독성이 크게 향상됩니다. 이 블로그에서 소개한 기본 개념과 예제를 바탕으로 직접 쿼리를 작성해 보면서 윈도우 함수의 강력함을 경험해 보세요.
참고 자료:
'Develop' 카테고리의 다른 글
GitLab에서 커밋 취소하는 방법: 완벽 가이드 🔥 (1) | 2025.04.03 |
---|---|
현대 JavaScript 마스터하기: ES6+와 함수형 프로그래밍의 완벽 가이드 (1) | 2025.03.26 |
[JavaScript] 숫자 콤마 표현할 때 소수점 표시하는 방법 (0) | 2025.03.20 |
[JavaScript] 소수점과 정수를 연산 했을 때 소수점으로 결과 출력 (0) | 2025.03.20 |
[JavaScript] 선택적 체이닝(Optional Chaining) 연산자: 코드를 더 안전하고 간결하게 만드는 방법 (1) | 2025.03.19 |