[DB] 데이터베이스 개념노트 21편 - Top N 쿼리, ROWNUM, FETCH FIRST

데이터베이스 개념노트 21편 - Top N 쿼리, ROWNUM, FETCH FIRST

지난 글에서는 행을 유지한 상태에서 순위와 누적값을 계산하는 윈도우 함수(Window Function)에 대해 정리했습니다.

이번 글에서는 SQLD에서 빠뜨리면 아쉬운 핵심 주제인 Top N 쿼리, ROWNUM, FETCH FIRST에 대해 정리해 보겠습니다.

Top N 쿼리는 말 그대로 상위 N개 또는 하위 N개의 데이터를 조회하는 SQL입니다. 예를 들어 주문금액 상위 3건, 최근 가입 회원 10명, 매출 상위 5개 상품을 조회할 때 사용합니다.


1. Top N 쿼리란?

Top N 쿼리는 정렬된 결과 중에서 상위 N개 또는 하위 N개의 행만 조회하는 SQL입니다.

Top N 쿼리는 특정 기준으로 정렬한 뒤, 그중 상위 또는 하위 일부 행만 가져오는 쿼리이다.

예를 들어 다음과 같은 상황에서 Top N 쿼리를 사용할 수 있습니다.

  • 주문금액이 가장 큰 주문 3건 조회
  • 최근 가입한 회원 10명 조회
  • 판매량이 가장 높은 상품 5개 조회
  • 카테고리별 매출 상위 3개 상품 조회
  • 게시글 목록에서 11번부터 20번까지 조회

2. 예제 테이블

이번 글에서는 다음 주문 테이블을 기준으로 예제를 살펴보겠습니다.

주문번호 회원명 카테고리 상품명 주문금액 주문일자
O001 김지훈 전자기기 키보드 30000 2026-01-10
O002 김지훈 전자기기 마우스 15000 2026-01-12
O003 이수진 가구 책상 120000 2026-01-15
O004 박민수 가구 의자 80000 2026-02-01
O005 이수진 전자기기 모니터 250000 2026-02-05
O006 최유진 전자기기 노트북 900000 2026-02-10

3. Top N 쿼리의 기본 흐름

Top N 쿼리는 보통 다음 순서로 생각하면 됩니다.

  1. 어떤 기준으로 정렬할지 정한다.
  2. 상위 몇 개를 가져올지 정한다.
  3. 정렬 결과에서 원하는 개수만 조회한다.

예를 들어 주문금액 상위 3건을 조회하려면 다음 흐름이 됩니다.

1. 주문금액 기준 내림차순 정렬
2. 정렬된 결과에서 앞의 3건만 조회

4. ORDER BY만 사용한 경우

주문금액이 큰 순서대로 전체 주문을 조회하려면 ORDER BY를 사용합니다.

SELECT 주문번호,
       회원명,
       상품명,
       주문금액
FROM 주문
ORDER BY 주문금액 DESC;

위 SQL은 주문금액이 큰 순서대로 전체 데이터를 정렬합니다. 하지만 상위 3건만 가져오지는 않습니다.

상위 3건만 조회하려면 ROWNUM, FETCH FIRST, 또는 ROW_NUMBER 같은 방법을 함께 사용해야 합니다.


5. ROWNUM이란?

ROWNUM은 Oracle에서 조회 결과에 순서대로 부여되는 가상의 행 번호입니다.

ROWNUM은 Oracle에서 SELECT 결과가 반환될 때 각 행에 임시로 붙는 번호이다.

간단히 말하면 조회되는 행에 1, 2, 3, 4처럼 번호를 붙여주는 가상 컬럼입니다.

SELECT ROWNUM,
       주문번호,
       회원명,
       주문금액
FROM 주문;

위 SQL은 주문 테이블의 각 행에 ROWNUM을 함께 출력합니다.


6. ROWNUM으로 상위 N건 조회하기

가장 단순한 형태의 Top N 쿼리는 ROWNUM을 사용해 앞의 N건만 조회하는 방식입니다.

SELECT 주문번호,
       회원명,
       상품명,
       주문금액
FROM 주문
WHERE ROWNUM <= 3;

위 SQL은 주문 테이블에서 먼저 조회되는 3건만 가져옵니다.

하지만 이 방식은 정렬 기준이 없습니다. 따라서 주문금액 상위 3건을 의미하지는 않습니다. 단순히 먼저 읽힌 3건일 뿐입니다.


7. 잘못된 Top N 예시

주문금액 상위 3건을 조회하려고 다음과 같이 작성하면 문제가 될 수 있습니다.

SELECT 주문번호,
       회원명,
       상품명,
       주문금액
FROM 주문
WHERE ROWNUM <= 3
ORDER BY 주문금액 DESC;

겉으로 보면 주문금액 기준으로 정렬한 뒤 3건을 가져오는 것처럼 보입니다. 하지만 실제로는 그렇지 않을 수 있습니다.

Oracle에서 ROWNUM은 ORDER BY보다 먼저 적용될 수 있습니다. 즉, 먼저 3건을 가져온 뒤 그 3건만 주문금액 기준으로 정렬하는 결과가 될 수 있습니다.

ROWNUM을 이용한 Top N 쿼리에서는 정렬을 먼저 하고, 그 결과에 ROWNUM 조건을 적용해야 합니다.


8. 올바른 ROWNUM Top N 쿼리

주문금액 상위 3건을 올바르게 조회하려면 인라인 뷰를 사용합니다.

SELECT *
FROM (
  SELECT 주문번호,
         회원명,
         상품명,
         주문금액
  FROM 주문
  ORDER BY 주문금액 DESC
)
WHERE ROWNUM <= 3;

이 SQL은 다음 순서로 이해하면 됩니다.

  1. 인라인 뷰 안에서 주문금액 기준으로 먼저 정렬한다.
  2. 정렬된 결과에서 바깥 쿼리가 ROWNUM <= 3 조건을 적용한다.
  3. 최종적으로 주문금액 상위 3건만 조회한다.

Top N 쿼리에서 가장 중요한 패턴 중 하나입니다.


9. 인라인 뷰란?

인라인 뷰는 FROM절 안에 작성하는 서브쿼리입니다.

FROM (
  SELECT ...
  FROM ...
  ORDER BY ...
)

Top N 쿼리에서는 인라인 뷰를 사용해 먼저 정렬된 결과를 만들고, 바깥쪽 SELECT에서 ROWNUM 조건을 적용합니다.

즉, 인라인 뷰는 Top N 쿼리에서 정렬 순서를 보장하기 위해 자주 사용됩니다.


10. 하위 N건 조회하기

하위 N건을 조회할 때도 같은 방식으로 작성할 수 있습니다. 주문금액이 낮은 순서로 3건을 조회해 보겠습니다.

SELECT *
FROM (
  SELECT 주문번호,
         회원명,
         상품명,
         주문금액
  FROM 주문
  ORDER BY 주문금액 ASC
)
WHERE ROWNUM <= 3;

ORDER BY 주문금액 ASC를 사용했기 때문에 주문금액이 낮은 3건이 조회됩니다.


11. ROWNUM > 3이 안 되는 이유

ROWNUM을 처음 공부할 때 자주 헷갈리는 부분이 있습니다. 다음 SQL은 원하는 결과가 나오지 않을 수 있습니다.

SELECT 주문번호,
       회원명,
       주문금액
FROM 주문
WHERE ROWNUM > 3;

ROWNUM은 행이 결과로 선택될 때 1부터 부여됩니다. 그런데 첫 번째 행이 ROWNUM = 1인 상태에서 ROWNUM > 3 조건을 만족하지 못하면 결과에 포함되지 않습니다. 다음 행도 다시 첫 번째 후보 행처럼 평가되므로 계속 조건을 만족하지 못합니다.

따라서 ROWNUM > 3 같은 조건은 단독으로 사용하면 결과가 나오지 않을 수 있습니다.


12. 특정 구간 조회하기

게시판 페이징처럼 4번째부터 10번째까지 조회하고 싶다면 ROWNUM에 별칭을 붙인 뒤 바깥쪽에서 필터링합니다.

SELECT *
FROM (
  SELECT ROWNUM AS RN,
         A.*
  FROM (
    SELECT 주문번호,
           회원명,
           상품명,
           주문금액
    FROM 주문
    ORDER BY 주문금액 DESC
  ) A
  WHERE ROWNUM <= 10
)
WHERE RN >= 4;

이 SQL은 다음 순서로 동작한다고 이해하면 됩니다.

  1. 주문금액 기준으로 정렬한다.
  2. 정렬된 결과에서 10번째까지 ROWNUM을 붙인다.
  3. 그중 RN이 4 이상인 행만 조회한다.

즉, 최종적으로 4번째부터 10번째까지의 행을 조회합니다.


13. FETCH FIRST란?

FETCH FIRST는 정렬된 결과에서 앞의 N건만 가져오는 문법입니다. ROWNUM보다 직관적으로 Top N 쿼리를 작성할 수 있습니다.

FETCH FIRST는 ORDER BY 결과 중 상위 N개의 행만 반환하는 문법이다.

주문금액 상위 3건을 조회하려면 다음과 같이 작성할 수 있습니다.

SELECT 주문번호,
       회원명,
       상품명,
       주문금액
FROM 주문
ORDER BY 주문금액 DESC
FETCH FIRST 3 ROWS ONLY;

위 SQL은 주문금액 기준으로 내림차순 정렬한 뒤, 앞의 3건만 조회합니다.


14. FETCH FIRST 문법 구조

FETCH FIRST의 기본 구조는 다음과 같습니다.

SELECT 컬럼명
FROM 테이블명
ORDER BY 정렬기준
FETCH FIRST N ROWS ONLY;
구문 의미
ORDER BY 어떤 기준으로 정렬할지 지정
FETCH FIRST N ROWS ONLY 정렬 결과 중 앞의 N건만 조회

FETCH FIRST는 ROWNUM보다 의미가 명확해서 Top N 쿼리를 읽기 쉽습니다.


15. FETCH FIRST와 OFFSET

OFFSET은 앞의 몇 개 행을 건너뛸지 지정하는 문법입니다. 게시판 페이징 처리에서 자주 사용됩니다.

SELECT 주문번호,
       회원명,
       상품명,
       주문금액
FROM 주문
ORDER BY 주문금액 DESC
OFFSET 3 ROWS
FETCH NEXT 3 ROWS ONLY;

위 SQL은 주문금액 기준으로 정렬한 뒤, 앞의 3건을 건너뛰고 다음 3건을 조회합니다.

구문 의미
OFFSET 3 ROWS 앞의 3개 행을 건너뜀
FETCH NEXT 3 ROWS ONLY 그 다음 3개 행을 조회

16. WITH TIES

WITH TIES는 마지막 순위와 같은 값을 가진 행까지 함께 조회할 때 사용합니다.

예를 들어 주문금액 상위 3건을 조회하는데, 3번째 주문과 같은 금액을 가진 주문이 더 있다면 함께 조회하고 싶을 수 있습니다.

SELECT 주문번호,
       회원명,
       상품명,
       주문금액
FROM 주문
ORDER BY 주문금액 DESC
FETCH FIRST 3 ROWS WITH TIES;

WITH TIES를 사용하면 경계에 걸린 동일한 정렬 값의 행까지 포함될 수 있습니다.

즉, 정확히 3건만 나오는 것이 아니라 동점이 있으면 3건보다 더 많이 나올 수 있습니다.


17. ROWNUM과 FETCH FIRST 비교

구분 ROWNUM FETCH FIRST
작성 방식 인라인 뷰와 함께 사용하는 경우가 많음 ORDER BY 뒤에 직접 작성
가독성 처리 순서를 이해해야 함 상위 N건 의미가 직관적
정렬 후 Top N 인라인 뷰 필요 ORDER BY와 함께 사용
페이징 중첩 서브쿼리 필요 OFFSET과 함께 사용 가능

18. ROW_NUMBER를 이용한 Top N

윈도우 함수인 ROW_NUMBER를 이용해 Top N 쿼리를 작성할 수도 있습니다.

SELECT *
FROM (
  SELECT 주문번호,
         회원명,
         상품명,
         주문금액,
         ROW_NUMBER() OVER (ORDER BY 주문금액 DESC) AS RN
  FROM 주문
)
WHERE RN <= 3;

위 SQL은 주문금액 기준으로 순번을 부여한 뒤, RN이 3 이하인 행만 조회합니다.

ROW_NUMBER 방식은 특히 그룹별 Top N을 구할 때 유용합니다.


19. 그룹별 Top N 쿼리

전체 상위 N건이 아니라 카테고리별 상위 N건을 조회하고 싶을 때가 있습니다. 예를 들어 카테고리별 주문금액 상위 2건을 조회해 보겠습니다.

SELECT *
FROM (
  SELECT 주문번호,
         카테고리,
         상품명,
         주문금액,
         ROW_NUMBER() OVER (
           PARTITION BY 카테고리
           ORDER BY 주문금액 DESC
         ) AS RN
  FROM 주문
)
WHERE RN <= 2;

이 SQL은 카테고리별로 주문금액 순위를 따로 매기고, 각 카테고리에서 상위 2건만 조회합니다.

PARTITION BY는 카테고리별로 그룹을 나누고, ORDER BY는 각 카테고리 안에서 주문금액 순서를 정합니다.


20. ROW_NUMBER, RANK, DENSE_RANK와 Top N

Top N 쿼리에서 동점 처리를 어떻게 할지에 따라 사용하는 함수가 달라질 수 있습니다.

함수 동점 처리 Top N 결과 특징
ROW_NUMBER 동점이어도 서로 다른 번호 부여 정확히 N건만 조회하기 쉬움
RANK 동점이면 같은 순위, 다음 순위 건너뜀 동점 포함 시 N건보다 많아질 수 있음
DENSE_RANK 동점이면 같은 순위, 다음 순위 건너뛰지 않음 순위 그룹 기준으로 조회할 때 유용

예를 들어 3등까지 조회할 때 3등이 여러 명이면 RANK나 DENSE_RANK를 사용한 결과는 3건보다 많아질 수 있습니다.


21. RANK를 이용한 동점 포함 Top N

동점 순위까지 포함해서 상위 3등을 조회하려면 RANK를 사용할 수 있습니다.

SELECT *
FROM (
  SELECT 주문번호,
         회원명,
         상품명,
         주문금액,
         RANK() OVER (ORDER BY 주문금액 DESC) AS 순위
  FROM 주문
)
WHERE 순위 <= 3;

이 SQL은 주문금액 기준으로 순위를 매긴 뒤 3등까지 조회합니다. 동점자가 있다면 3건보다 더 많이 조회될 수 있습니다.


22. Top N 쿼리와 NULL 정렬

Top N 쿼리는 정렬 기준이 중요합니다. 정렬 컬럼에 NULL이 있을 경우 NULL이 어디에 위치하는지도 결과에 영향을 줄 수 있습니다.

Oracle에서는 ORDER BY에서 NULLS FIRST, NULLS LAST를 사용할 수 있습니다.

SELECT 주문번호,
       회원명,
       주문금액
FROM 주문
ORDER BY 주문금액 DESC NULLS LAST
FETCH FIRST 3 ROWS ONLY;

위 SQL은 주문금액이 큰 순서로 정렬하되, NULL 값은 마지막에 배치합니다.

구문 의미
NULLS FIRST NULL 값을 먼저 정렬
NULLS LAST NULL 값을 나중에 정렬

23. Top N 쿼리와 ORDER BY 중요성

Top N 쿼리에서 ORDER BY가 없으면 상위 N건의 의미가 불명확해집니다.

SELECT *
FROM 주문
FETCH FIRST 3 ROWS ONLY;

위 SQL은 3건을 가져오기는 하지만 어떤 기준의 상위 3건인지는 알 수 없습니다. 따라서 Top N 쿼리에서는 ORDER BY를 함께 사용하는 것이 중요합니다.

Top N 쿼리는 반드시 어떤 기준의 Top N인지 ORDER BY로 명확히 하는 것이 좋습니다.


24. Top N 쿼리와 인덱스

Top N 쿼리는 정렬이 중요하기 때문에 인덱스와도 관련이 있습니다. 정렬 기준 컬럼에 적절한 인덱스가 있으면 Top N 조회 성능에 도움이 될 수 있습니다.

CREATE INDEX IDX_주문_주문금액
ON 주문(주문금액);

주문금액 기준으로 자주 Top N 조회를 한다면 주문금액 컬럼의 인덱스를 검토할 수 있습니다.

다만 인덱스는 SELECT 성능에는 도움이 될 수 있지만, INSERT, UPDATE, DELETE 작업에는 부담을 줄 수 있으므로 신중하게 설계해야 합니다.


25. Top N 쿼리에서 자주 하는 실수

실수 주의할 점
ROWNUM 조건을 ORDER BY와 같은 단계에서 사용 먼저 일부 행을 가져온 뒤 정렬될 수 있으므로 인라인 뷰 사용
ROWNUM > N 조건을 단독으로 사용 원하는 결과가 나오지 않을 수 있음
ORDER BY 없이 FETCH FIRST 사용 어떤 기준의 Top N인지 불명확함
ROW_NUMBER와 RANK의 동점 처리를 혼동 ROW_NUMBER는 고유 번호, RANK는 동점 같은 순위
그룹별 Top N에 단순 ROWNUM 사용 그룹별 Top N은 ROW_NUMBER와 PARTITION BY 활용

26. SQLD 관점에서 꼭 기억할 내용

이번 글에서 SQLD 공부를 위해 꼭 기억해야 할 내용은 다음과 같습니다.

  • Top N 쿼리는 정렬 결과 중 상위 또는 하위 N건을 조회하는 쿼리이다.
  • ROWNUM은 Oracle에서 조회 결과에 임시로 부여되는 행 번호이다.
  • ROWNUM은 ORDER BY보다 먼저 적용될 수 있으므로 주의해야 한다.
  • 정렬 후 ROWNUM 조건을 적용하려면 인라인 뷰를 사용한다.
  • ROWNUM > N 조건은 단독으로 사용하면 원하는 결과가 나오지 않을 수 있다.
  • FETCH FIRST N ROWS ONLY는 상위 N건을 직관적으로 조회하는 문법이다.
  • OFFSET은 앞의 행을 건너뛸 때 사용한다.
  • WITH TIES는 마지막 순위와 같은 값을 가진 행까지 포함한다.
  • 그룹별 Top N은 ROW_NUMBER와 PARTITION BY를 활용할 수 있다.
  • Top N 쿼리에서는 ORDER BY 기준이 매우 중요하다.

27. 핵심 요약

개념 핵심 내용
Top N 쿼리 정렬 결과 중 상위 또는 하위 N건을 조회하는 쿼리
ROWNUM Oracle에서 조회 결과에 부여되는 가상의 행 번호
인라인 뷰 FROM절 안의 서브쿼리로, 정렬 후 ROWNUM 적용 시 사용
FETCH FIRST 정렬 결과에서 앞의 N건만 조회하는 문법
OFFSET 앞의 행을 건너뛰고 다음 행부터 조회하는 문법
WITH TIES 마지막 순위와 같은 정렬 값을 가진 행까지 포함
ROW_NUMBER 각 행에 고유 순번을 부여하여 Top N 조회 가능
RANK 동점 순위를 포함한 Top N 조회에 활용 가능

28. 마무리

이번 글에서는 SQLD에서 빠뜨리면 아쉬운 Top N 쿼리, ROWNUM, FETCH FIRST에 대해 정리했습니다.

Top N 쿼리는 단순히 몇 개의 행을 가져오는 것이 아니라, 반드시 어떤 기준으로 정렬한 뒤 상위 또는 하위 데이터를 가져오는 방식으로 이해해야 합니다.

ROWNUM을 사용할 때는 ORDER BY와의 처리 순서를 주의해야 하며, 정렬 후 Top N을 구하려면 인라인 뷰를 사용하는 패턴을 기억해야 합니다. FETCH FIRST는 더 직관적으로 상위 N건을 조회할 수 있는 문법입니다.

또한 그룹별 Top N을 구할 때는 ROW_NUMBER, RANK, DENSE_RANK 같은 윈도우 함수와 PARTITION BY를 함께 활용할 수 있습니다.

다음 글에서는  계층형 질의 START WITH, CONNECT BY PRIOR에 대해 정리해 보겠습니다.