SQLmate
SQL 과목 · 업데이트 2026-05-01

SQLD 윈도우 함수 정리 — OVER 절 한 번에 이해하기

한 줄 요약 — 윈도우 함수는 함수() OVER (PARTITION BY ... ORDER BY ... ROWS/RANGE BETWEEN ...) 구조. 순위·집계·행 참조 세 종류로 묶고, ROW_NUMBER vs RANK vs DENSE_RANK 차이와 ROWS/RANGE 프레임만 잡으면 SQLD 윈도우 문제는 거의 해결된다.

윈도우 함수는 GROUP BY 집계와 달리 결과 행을 줄이지 않으면서 그룹별 계산을 수행한다. SQLD 2과목 후반에 등장하는 단골 출제 영역이고, 처음 보는 수험생이 가장 많이 헷갈리는 부분이다. 이 글은 OVER 절의 구성 요소를 분해해 설명하고, 자주 나오는 출제 패턴과 함정을 한 번에 정리한다.

윈도우 함수의 정의 (ANSI SQL 2003)

윈도우 함수(Window Function)는 ANSI SQL 2003에 표준으로 추가된 분석 함수다. 일반 집계 함수가 GROUP BY로 행을 묶어 결과 행을 줄이는 반면, 윈도우 함수는 각 행마다 윈도우(이웃 행 집합)를 정의해 그 위에서 계산한다. 결과 행 수는 그대로 유지된다.

기본 구조는 다음과 같다.

함수() OVER (
  PARTITION BY 그룹기준컬럼   -- 윈도우를 어떤 단위로 나눌지 (없으면 전체가 한 윈도우)
  ORDER BY 정렬컬럼            -- 윈도우 안의 정렬 (순위·LAG/LEAD에 필수)
  ROWS BETWEEN ... AND ...    -- 윈도우의 물리적 행 범위 (선택)
)

PARTITION BY가 없으면 테이블 전체가 하나의 윈도우. 순위 함수와 LAG/LEAD는 의미 있는 결과를 위해 보통 ORDER BY를 지정해야 하며, DBMS에 따라 문법 필수 여부가 다르다(SQLite처럼 ORDER BY를 생략하면 임의 순서나 모든 행 peer 처리로 동작하는 경우도 있다). 단순 집계(SUM, AVG)에서는 누적 계산을 만들고 싶을 때만 쓴다.

윈도우 함수의 3가지 분류

분류대표 함수용도
순위ROW_NUMBER, RANK, DENSE_RANK, NTILE순위 매기기, 분위 나누기
집계SUM, AVG, COUNT, MIN, MAX (OVER)누적 합·이동 평균·그룹 비율
행 참조LAG, LEAD, FIRST_VALUE, LAST_VALUE이전/다음 행, 그룹 첫/마지막 행 값

순위 함수 3종 차이는 동점 처리 방식에서 갈린다.

점수ROW_NUMBERRANKDENSE_RANK
100111
100211
90332
80443

ROW_NUMBER는 동점이어도 무조건 다른 번호. RANK는 동점은 같은 순위이고 다음 순위는 건너뛰어 3. DENSE_RANK는 동점은 같은 순위이지만 다음 순위는 건너뛰지 않고 2.

예제 코드 (실행 가능)

다음 sales 테이블을 가정한다. 실제 실행 시에는 아래 스키마와 데이터를 먼저 만들어야 한다.

CREATE TABLE sales (
  sale_id   INTEGER PRIMARY KEY,
  dept_id   INTEGER,
  emp_name  VARCHAR(50),
  amount    INTEGER,
  sale_date DATE
);

INSERT INTO sales VALUES
  (1, 10, '김민수', 1000, '2026-04-01'),
  (2, 10, '이서연', 1500, '2026-04-02'),
  (3, 10, '박지훈', 1500, '2026-04-03'),
  (4, 20, '최유진',  800, '2026-04-01'),
  (5, 20, '정태호', 2000, '2026-04-02');
sale_iddept_idemp_nameamountsale_date
110김민수10002026-04-01
210이서연15002026-04-02
310박지훈15002026-04-03
420최유진8002026-04-01
520정태호20002026-04-02

ROW_NUMBER · RANK · DENSE_RANK 비교

SELECT emp_name, dept_id, amount,
       ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY amount DESC) AS rn,
       RANK()       OVER (PARTITION BY dept_id ORDER BY amount DESC) AS rk,
       DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY amount DESC) AS dr
  FROM sales;

각 부서 안에서 매출 내림차순 순위. 부서 10에서 1500이 두 번 나오면 ROW_NUMBER는 1, 2를, RANK는 1, 1, 3을, DENSE_RANK는 1, 1, 2를 매긴다.

누적 합 (집계 윈도우)

SELECT sale_date, dept_id, amount,
       SUM(amount) OVER (PARTITION BY dept_id
                         ORDER BY sale_date
                         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum_sum
  FROM sales;

부서별 일자 순서로 누적 합계. ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW가 시작부터 현재 행까지 누적 범위를 정의한다.

그룹 비율 (PARTITION 전체 합 대비)

SELECT emp_name, dept_id, amount,
       amount * 1.0 / SUM(amount) OVER (PARTITION BY dept_id) AS dept_ratio
  FROM sales;

ORDER BY 없이 PARTITION BY만 쓰면 그 그룹의 전체 합이 매 행에 들어간다.

amount가 INTEGER 타입일 때 amount / SUM(...)만 쓰면 PostgreSQL·SQL Server 등에서는 정수 나눗셈이 일어나 결과가 0으로 잘려 나올 수 있다. amount * 1.0 또는 CAST(amount AS NUMERIC)처럼 한쪽을 실수로 변환해야 비율이 제대로 계산된다.

LAG / LEAD — 이전/다음 행 참조

SELECT emp_name, sale_date, amount,
       LAG(amount, 1)  OVER (PARTITION BY dept_id ORDER BY sale_date) AS prev_amount,
       LEAD(amount, 1) OVER (PARTITION BY dept_id ORDER BY sale_date) AS next_amount
  FROM sales;

LAG(컬럼, N)은 같은 윈도우에서 N행 전의 값, LEAD는 N행 뒤의 값을 가져온다. 첫/마지막 행에서는 NULL.

NTILE — 분위 나누기

SELECT emp_name, amount,
       NTILE(4) OVER (ORDER BY amount DESC) AS quartile
  FROM sales;

전체를 매출 내림차순으로 4분위로 나눠 1~4 그룹으로 라벨링.

SQLD 출제 패턴

SQLD는 50문항 중 데이터 모델링의 이해 10문항 + SQL 기본 및 활용 40문항으로 구성된다(KDATA 공식). 윈도우 함수는 2과목 후반의 단골이다.

자주 나오는 형태는 네 가지.

  1. 순위 함수 결과 비교: 동점이 있는 데이터에 ROW_NUMBER · RANK · DENSE_RANK를 적용했을 때 결과 비교
  2. PARTITION BY 효과: PARTITION BY가 있을 때와 없을 때의 결과 차이
  3. ROWS vs RANGE 프레임: 같은 정렬에서 ROWS BETWEEN과 RANGE BETWEEN이 다른 결과를 내는 사례
  4. LAG/LEAD 결과 예측: 첫 행/마지막 행에서 NULL이 나오는 위치 찾기

회차별 정확한 출제 통계는 KDATA가 공개하지 않는다. SQL 전문가 가이드(KDATA 공식 수험서) 수록 비중과 합격 후기 종합으로는 윈도우 함수가 SQLD 2과목 후반 단골 영역으로 등장하는 것으로 알려져 있으나, 공식 회차별 문항 수는 비공개다.

흔한 함정

1. ORDER BY 없이 누적 집계

집계 함수에 OVER만 붙이고 ORDER BY를 빼면 PARTITION 전체 합이 들어가지, 누적 합이 되지 않는다. 누적이 필요하면 반드시 ORDER BY + (선택적으로) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.

2. ROWS vs RANGE의 차이

ROWS는 물리적 행 개수를, RANGE는 ORDER BY 컬럼의 값 범위를 기준으로 한다. ORDER BY 컬럼에 동점이 있을 때 차이가 드러난다.

-- 같은 점수가 여러 명일 때
ROWS  BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW   -- 자기 행까지만
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW   -- 같은 값 모든 행 포함

ORDER BY 절을 ROWS/RANGE 명시 없이 쓰면 DBMS 기본은 보통 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW로 적용된다. 시험에서는 두 결과 차이를 묻는 문제가 단골.

3. WHERE 절에서 윈도우 함수 사용 불가

윈도우 함수는 SELECT 절과 ORDER BY에서만 사용 가능하다. WHERE/HAVING/GROUP BY에서는 직접 호출할 수 없다. "각 부서에서 1등인 사원만" 같은 조건은 인라인 뷰로 한 번 감싸서 외부에서 필터링한다.

-- 잘못된 표기 (에러)
SELECT * FROM sales
 WHERE ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY amount DESC) = 1;

-- 올바른 표기
SELECT *
  FROM (SELECT s.*,
               ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY amount DESC) AS rn
          FROM sales s) t
 WHERE rn = 1;

4. LAST_VALUE의 기본 프레임 함정

LAST_VALUE() OVER (ORDER BY ...)만 쓰면 기본 프레임이 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW라서 "현재까지의 마지막 값"이 되어, 직관적으로 기대하는 "그룹 전체의 마지막 값"과 다르다. 그룹 전체의 마지막 값을 원하면 프레임을 명시해야 한다.

LAST_VALUE(amount) OVER (
  PARTITION BY dept_id
  ORDER BY sale_date
  ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)

SQLmate에서 직접 실행

윈도우 함수는 결과를 머리로 그리기 가장 어려운 영역이다. SQLmate iOS·Android 앱의 WASM SQLite 샌드박스에서 위 예제를 직접 실행하면 ROW_NUMBER와 RANK가 동점 데이터에서 어떻게 다른 값을 내는지, ROWS와 RANGE가 같은 ORDER BY에서 어떻게 다른 누적을 만드는지 한눈에 들어온다. 시험일정 카운트다운과 함께 홈에서 앱 받기.

FAQ

Q. ROW_NUMBER, RANK, DENSE_RANK 중 SQLD에서 가장 자주 나오는 건?

셋 다 자주 나오지만, 세 함수의 결과 차이를 묻는 비교 문제가 가장 많은 비중입니다. 동점 데이터를 주고 세 함수 결과를 각각 매기게 하거나, 하나의 결과만 주고 어떤 함수인지를 묻습니다. 셋의 차이를 한 줄로 정리하면 ROW_NUMBER는 무조건 1,2,3,4, RANK는 1,1,3,4(동점 다음 건너뜀), DENSE_RANK는 1,1,2,3(연속).

Q. PARTITION BY 없이 OVER 절만 쓰면 어떻게 되나요?

테이블 전체가 하나의 윈도우로 취급됩니다. SUM(salary) OVER ()는 전체 직원 급여 합계가 모든 행에 똑같이 들어갑니다. 비율을 계산할 때 자주 쓰는 형태입니다. 시험 문제에서 PARTITION BY가 빠진 OVER 절의 결과를 묻는 유형이 있으니 "전체 = 한 윈도우"를 외워두면 좋습니다.

Q. 윈도우 함수와 GROUP BY를 동시에 쓸 수 있나요?

가능합니다. GROUP BY로 먼저 그룹별 집계를 만든 결과 위에 윈도우 함수가 적용됩니다. 예를 들어 SELECT dept_id, AVG(salary), RANK() OVER (ORDER BY AVG(salary) DESC) FROM employee GROUP BY dept_id처럼 부서별 평균 급여에 순위를 매길 수 있습니다. 다만 SELECT 절의 일반 컬럼은 모두 GROUP BY 안에 있어야 합니다.

Q. SQLD에서 ROWS와 RANGE 프레임은 자주 출제되나요?

출제 빈도가 높지 않은 편이지만, 한 번 나오면 정답률이 매우 낮은 영역입니다. 둘의 차이가 드러나려면 ORDER BY 컬럼에 동점이 있어야 하므로, 동점이 포함된 보기 데이터가 보이면 RANGE/ROWS 차이를 의심해봐야 합니다. 실무에서도 자주 만나는 함정이라 알아두면 도움이 됩니다.

마무리

윈도우 함수는 OVER 절의 세 구성 요소(PARTITION BY · ORDER BY · 프레임)와 세 함수 분류(순위·집계·행 참조)를 정리하면 시험 범위가 좁아진다. 함수 이름을 외우는 것보다 각 OVER 절이 만드는 윈도우의 모양을 그릴 줄 아는 게 핵심이다. 동점 데이터에 직접 쿼리를 돌려보는 게 가장 빠르다.


같이 읽으면 좋은 글

출처 / 참고
한국데이터산업진흥원(KDATA) dataq.or.kr · SQL 전문가 가이드(공식 수험서) · ANSI SQL 2003 윈도우 함수 표준.

합격을 보장하지 않습니다. 본인 준비 상황에 맞춰 판단해 주세요.