"부서별로 매출이 가장 높은 직원과 그 매출액을 한 행에 같이 보여줘 주세요."
이런 요청을 받으면 막막해지신 적 있으신가요? GROUP BY로 부서별 최댓값은 구할 수 있지만, 그 최댓값을 낸 직원 이름까지 같은 행에 담으려면 서브쿼리를 여러 겹 겹치게 되죠. 이럴 때 필요한 게 바로 윈도우 함수예요.
윈도우 함수는 SQL 실무와 면접에서 초급과 중급 실력자를 가르는 대표적인 기준으로 꼽혀요. GROUP BY만 알고 있는 분과, OVER()까지 다룰 줄 아는 분은 풀 수 있는 문제의 범위 자체가 달라져요. 이번 글에서 개념부터 실무에서 바로 쓸 수 있는 예제 쿼리까지 정리해볼게요.
1. 윈도우 함수란 무엇인가요?
윈도우 함수는 행들을 특정 그룹(윈도우)으로 묶어서 계산하면서도, GROUP BY와 달리 원본 행을 그대로 유지하는 함수예요. 집계 함수와 달리 윈도우 함수는 결과가 하나의 행으로 합쳐지지 않고, 각 행이 자신의 정체성을 그대로 유지한 채로 순위·누적합·이전 행과의 비교 같은 계산이 추가로 붙는 방식이에요[3].
즉, "부서별 평균 매출은 얼마인가요?"처럼 행이 하나로 줄어드는 질문은 GROUP BY로 충분하지만, "각 직원의 매출과 함께 그 직원이 부서 내에서 몇 위인지도 같이 보여줘 주세요"처럼 원본 행 + 추가 계산값이 동시에 필요한 질문은 윈도우 함수가 답이에요.
2. OVER(), PARTITION BY, ORDER BY 기본 문법
함수명(...) OVER (
PARTITION BY 그룹기준컬럼
ORDER BY 정렬기준컬럼
)OVER(): 이 함수가 윈도우 함수임을 알리고, 계산 범위를 정의하는 부분이에요. OVER()만 있고 PARTITION BY / ORDER BY가 없으면 테이블 전체가 하나의 그룹이 돼요.
PARTITION BY: 행들을 소그룹으로 나누는 기준이에요. 예를 들어
PARTITION BY department라면 부서별로 각각 별도의 계산이 이루어져요.ORDER BY: 그룹(파티션) 안에서 행의 순서를 정해요. 순위 함수에서는 순위를 매기는 기준이 되고, 누적합/이동평균 같은 계산에서는 어떤 순서로 누적할지를 결정해요.
참고로 윈도우 함수는 SELECT 절과 ORDER BY 절에서만 사용할 수 있고, WHERE·GROUP BY·HAVING 절에는 직접 쓸 수 없다는 점도 알아두면 좋아요[1]. 윈도우 함수로 계산한 값을 다시 필터링하려면 서브쿼리로 한 번 감싸야 해요. (4장에서 실제로 이 패턴을 사용해요.)
3. 데이터 분석가가 가장 많이 쓰는 윈도우 함수
3.1 ROW_NUMBER — 그룹 내 고유 순번
ROW_NUMBER()는 파티션 안에서 각 행에 중복 없는 고유한 순번을 1부터 매겨요[1][2]. 값이 같아도 무조건 다른 번호가 부여돼요.
SELECT
department,
employee,
amount,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY amount DESC) AS rn
FROM sales;3.2 RANK / DENSE_RANK — 동점을 처리하는 순위
RANK()와 DENSE_RANK()는 동점(peer)에게 같은 순위를 부여한다는 점은 같지만, 그다음 순위를 매기는 방식이 달라요[1][2].
RANK(): 동점 다음 순위에 공백(gap) 이 생겨요. 1등이 2명이면 다음은 3등이에요.DENSE_RANK(): 동점 다음 순위가 연속으로 이어져요. 1등이 2명이어도 다음은 2등이에요.
SELECT
department,
employee,
amount,
RANK() OVER (PARTITION BY department ORDER BY amount DESC) AS rank_no,
DENSE_RANK() OVER (PARTITION BY department ORDER BY amount DESC) AS dense_rank_no
FROM sales;3.3 LAG / LEAD — 이전/다음 행 값 가져오기
LAG()는 현재 행 기준으로 이전 행의 값을, LEAD()는 다음 행의 값을 가져와요[1][2]. 전월 대비 증감처럼 "바로 앞/뒤 데이터와 비교"할 때 자주 써요.
SELECT
department,
sale_month,
monthly_amount,
LAG(monthly_amount, 1) OVER (PARTITION BY department ORDER BY sale_month) AS prev_month_amount,
monthly_amount - LAG(monthly_amount, 1) OVER (PARTITION BY department ORDER BY sale_month) AS mom_change
FROM monthly_sales;3.4 SUM / AVG OVER — 누적 합계와 이동 평균
집계 함수도 OVER를 붙이면 윈도우 함수로 동작해요[1][2]. ORDER BY만 주면 누적 합계가 되고, ROWS BETWEEN으로 범위를 지정하면 이동 평균을 만들 수 있어요.
-- 누적 합계
SELECT
sale_date,
daily_amount,
SUM(daily_amount) OVER (ORDER BY sale_date) AS running_total
FROM daily_sales;
-- 7일 이동 평균
SELECT
sale_date,
daily_amount,
AVG(daily_amount) OVER (
ORDER BY sale_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg_7d
FROM daily_sales;3.5 NTILE — 그룹을 N등분
NTILE(n)은 파티션 안의 행을 순서대로 최대한 균등하게 n개의 그룹으로 나눠요[1][2]. 상위 25% 고객, 매출 4분위 구간 나누기처럼 분위수 분석에 유용해요.
SELECT
employee,
amount,
NTILE(4) OVER (ORDER BY amount DESC) AS quartile
FROM sales;요약 표
함수 | 용도 | 실무 예시 |
|---|---|---|
ROW_NUMBER() | 그룹 내 고유 순번 부여 | 그룹별 최신 행 하나만 남기기(중복 제거) |
RANK() | 동점 허용 순위, 동점 뒤 순위 건너뜀 | 카테고리별 매출 순위 |
DENSE_RANK() | 동점 허용 순위, 건너뜀 없이 연속 | 동점자가 많은 항목 순위 매기기 |
LAG() / LEAD() | 이전/다음 행 값 조회 | 전월 대비 매출 증감률 |
SUM() OVER | 누적 합계 | 일별 누적 매출 |
AVG() OVER | 이동 평균 | 7일 이동평균 매출 |
NTILE(n) | 그룹을 N등분 | 매출 상위 25% 고객 추출 |
4. 실무 활용 사례 3가지
4.1 그룹별 최신 레코드만 남기기 (중복 제거)
같은 사용자가 여러 번 정보를 갱신한 로그성 테이블에서, 사용자별 가장 최근 행만 남기고 싶을 때 자주 쓰는 패턴이에요[3]. ROW_NUMBER()로 순번을 매긴 뒤, 서브쿼리로 감싸서 rn = 1인 행만 필터링해요.
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC) AS rn
FROM user_profiles
) t
WHERE rn = 1;4.2 카테고리 내 순위 매기기 (카테고리별 매출 TOP 3)
전체 상품 중 순위가 아니라 카테고리별로 상위 몇 개만 뽑고 싶을 때, PARTITION BY category로 그룹을 나누고 RANK()로 순위를 매긴 다음 필터링하면 돼요.
SELECT *
FROM (
SELECT
category,
product_name,
total_sales,
RANK() OVER (PARTITION BY category ORDER BY total_sales DESC) AS category_rank
FROM product_sales
) t
WHERE category_rank <= 3;4.3 누적 합계 / 이동 평균으로 트렌드 보기
일별 매출이 들쭉날쭉할 때, 하루 값만 보면 트렌드가 잘 안 보여요. 앞서 3.4에서 본 SUM() OVER로 누적 매출을, AVG() OVER ... ROWS BETWEEN으로 7일 이동평균을 같이 뽑으면 단기 변동을 걸러낸 추세를 확인할 수 있어요. 리텐션 분석에서 코호트별 누적 잔존 사용자 수를 볼 때도 같은 패턴이 쓰여요.
5. 왜 지금 배워야 할까요?
윈도우 함수는 SQL 인터뷰에서 자주 등장하는 주제로 알려져 있어요[4]. 순위를 매기는 문제, 중복을 제거하는 문제, 누적 합계를 구하는 문제는 GROUP BY만으로는 깔끔하게 풀리지 않기 때문에, 지원자가 실무형 SQL을 다룰 수 있는지 확인하는 용도로 즐겨 쓰이는 편이에요[4].
그리고 이건 면접만을 위한 스킬이 아니에요. 대시보드에서 "카테고리별 TOP N", "전주 대비 증감", "코호트 리텐션" 같은 요청은 실무에서 정말 자주 나오고, 그때마다 서브쿼리를 여러 겹 쌓는 대신 윈도우 함수 한 줄로 해결할 수 있어요. 트리업의 기술 가이드에서 SQL 학습 로드맵을 확인하면서, 오늘 배운 예제들을 실제 테이블에 직접 돌려보는 걸 추천해요.
마무리
GROUP BY로 행이 줄어드는 문제와, 원본 행을 유지하면서 순위·누적·비교가 필요한 문제는 다른 도구가 필요해요. 그 경계에서 있는 게 바로 윈도우 함수예요. ROW_NUMBER, RANK, DENSE_RANK, LAG/LEAD, SUM/AVG OVER, NTILE — 이 7가지만 확실히 익혀도 대부분의 실무·면접 쿼리를 커버할 수 있어요.
한 번에 다 외우려 하지 말고, 오늘 나온 예제 쿼리를 하나씩 직접 실행해보면서 익혀보세요. 작은 연습들이 쌓이면 어느새 서브쿼리 대신 OVER()가 먼저 떠오르는 순간이 올 거예요.