공공부하자개발 · 영어 학습 노트
SQL
SQL 실무페이징·검색·이력·통계·채번·이관0/9 완료
  • 01페이징 쿼리
  • 02동적 검색 조건
  • 03이력 테이블과 시점 조회
  • 04기간별 통계 보고서
  • 05중복 데이터 찾기와 정리
  • 06트랜잭션 기초
  • 07채번과 순번 관리
  • 08저장 프로시저·함수 기초
  • 09데이터 이관·검증 쿼리
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › SQL 실무 › 01 / 9

페이징 쿼리

ROWNUM, LIMIT, OFFSET FETCH, 키셋 페이징
섹션 6진행 0 / 9
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6정리‹ 이전다음 ›

4. 응용 변형 예제

소스: sql-src/work_01_paging/02_dialect_paging.sql, 03_keyset.sql. 아래 방언 예제는 모두 페이지 2(17번부터 8번, 10행)를 가져옵니다.

예제 5: Oracle ROWNUM

SET MODE Oracle 로 실행했습니다. 안쪽에서 정렬하고, 그 결과에 ROWNUM 을 붙여 <= 20 으로 자른 뒤, 바깥에서 rn > 10 으로 앞 10 행을 버립니다. 인라인 뷰의 별칭에는 AS 를 쓰지 않습니다(Oracle 은 오류이고 H2 에서 재현 안 됨, 문법 검토만).

sql
SELECT
       id
     , title
     , created_at
  FROM (
        SELECT
               t.id
             , t.title
             , t.created_at
             , ROWNUM rn
          FROM (
                SELECT id, title, created_at
                  FROM board
                 ORDER BY created_at DESC, id DESC
               ) t
         WHERE ROWNUM <= 20
       )
 WHERE rn > 10
 ORDER BY rn;
text
ID | TITLE     | CREATED_AT
---+-----------+-----------
17 | 게시글 17 | 2026-09-17
16 | 게시글 16 | 2026-09-16
15 | 게시글 15 | 2026-09-15
14 | 게시글 14 | 2026-09-14
13 | 게시글 13 | 2026-09-13
12 | 게시글 12 | 2026-09-12
11 | 게시글 11 | 2026-09-11
10 | 게시글 10 | 2026-09-10
9  | 게시글 09 | 2026-09-09
8  | 게시글 08 | 2026-09-07
(10행)

안쪽 ROWNUM <= 20 은 정렬된 전체 행에 번호를 붙이지 않고 20 행에서 멈추게 하려는 것입니다. 흔한 실수는 ROWNUM 을 바깥에서 바로 비교하는 것입니다.

sql
SELECT id, title FROM board WHERE ROWNUM > 10;
text
ID | TITLE
---+------
(0행)

첫 행에 ROWNUM 1 이 붙고 1 > 10 이 거짓이라 버려집니다. 다음 행이 다시 1 을 받으므로 끝까지 한 행도 통과하지 못합니다. ROWNUM > 1 이나 ROWNUM = 2 도 마찬가지로 0건입니다.

예제 6: ROW_NUMBER 방식

표준 분석 함수(고급 01 순위 함수)로 번호를 매기고 범위로 거릅니다. WHERE 에는 분석 함수를 직접 못 쓰므로 인라인 뷰가 필요합니다. ROWNUM 보다 읽기 쉽고 Oracle·MySQL 8.0·MSSQL 에서 모두 됩니다.

sql
SELECT
       id
     , title
     , created_at
  FROM (
        SELECT
               id
             , title
             , created_at
             , ROW_NUMBER() OVER (ORDER BY created_at DESC, id DESC) AS rn
          FROM board
       ) t
 WHERE rn BETWEEN 11 AND 20
 ORDER BY rn;
text
ID | TITLE     | CREATED_AT
---+-----------+-----------
17 | 게시글 17 | 2026-09-17
16 | 게시글 16 | 2026-09-16
15 | 게시글 15 | 2026-09-15
14 | 게시글 14 | 2026-09-14
13 | 게시글 13 | 2026-09-13
12 | 게시글 12 | 2026-09-12
11 | 게시글 11 | 2026-09-11
10 | 게시글 10 | 2026-09-10
9  | 게시글 09 | 2026-09-09
8  | 게시글 08 | 2026-09-07
(10행)

바깥에 ORDER BY rn 이 없으면 인라인 뷰의 정렬 순서가 유지된다고 보장되지 않으므로 항상 붙입니다. 전체 행에 번호를 붙이는 비용이 있어서 표준 OFFSET FETCH 가 되는 DB 라면 그쪽이 낫습니다.

예제 7: MySQL LIMIT 와 MSSQL OFFSET FETCH·TOP

SET MODE MySQL, SET MODE MSSQLServer 로 실행했고 아래 세 문장은 모두 예제 6 과 같은 10 행을 돌려줍니다.

sql
-- MySQL
SELECT id, title, created_at FROM board ORDER BY created_at DESC, id DESC LIMIT 10 OFFSET 10;
SELECT id, title, created_at FROM board ORDER BY created_at DESC, id DESC LIMIT 10, 10;
sql
-- MSSQL
SELECT id, title, created_at FROM board ORDER BY created_at DESC, id DESC OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;

MSSQL 의 TOP 은 첫 페이지 전용입니다. 앞을 건너뛰는 기능이 없어서 페이지 2 이상은 OFFSET FETCH(2012 부터) 나 ROW_NUMBER() 를 씁니다.

sql
SELECT TOP 10 id, title, created_at FROM board ORDER BY created_at DESC, id DESC;
text
ID | TITLE     | CREATED_AT
---+-----------+-----------
27 | 게시글 27 | 2026-09-26
26 | 게시글 26 | 2026-09-24
25 | 게시글 25 | 2026-09-24
24 | 게시글 24 | 2026-09-24
23 | 게시글 23 | 2026-09-23
22 | 게시글 22 | 2026-09-22
21 | 게시글 21 | 2026-09-21
20 | 게시글 20 | 2026-09-20
19 | 게시글 19 | 2026-09-19
18 | 게시글 18 | 2026-09-17
(10행)
주의

MSSQL 의 OFFSET FETCH 는 ORDER BY 가 없으면 문법 오류입니다. MySQL 의 LIMIT 는 ORDER BY 없이도 실행되지만 결과 순서가 불확실합니다. H2 의 방언 모드는 이 차이를 완전히 재현하지 않으므로 실제 DB 동작은 위 규칙을 기준으로 봅니다.

예제 8: 키셋 페이징

페이지 1 의 마지막 행이 (2026-09-17, 18) 이라고 하고, 그 뒤 10 행을 가져옵니다. 조건값은 WITH 의 파라미터 CTE 로 두었습니다. 실제로는 바인드 변수 :last_created, :last_id 로 받습니다.

sql
WITH cur AS (SELECT DATE '2026-09-17' AS last_created, 18 AS last_id)
SELECT
       b.id
     , b.title
     , b.created_at
  FROM board b
 CROSS JOIN cur
 WHERE b.created_at < cur.last_created
    OR (b.created_at = cur.last_created AND b.id < cur.last_id)
 ORDER BY b.created_at DESC, b.id DESC
 FETCH FIRST 10 ROWS ONLY;
text
ID | TITLE     | CREATED_AT
---+-----------+-----------
17 | 게시글 17 | 2026-09-17
16 | 게시글 16 | 2026-09-16
15 | 게시글 15 | 2026-09-15
14 | 게시글 14 | 2026-09-14
13 | 게시글 13 | 2026-09-13
12 | 게시글 12 | 2026-09-12
11 | 게시글 11 | 2026-09-11
10 | 게시글 10 | 2026-09-10
9  | 게시글 09 | 2026-09-09
8  | 게시글 08 | 2026-09-07
(10행)

OFFSET 이 없습니다. 페이지 2 와 결과가 같지만 앞 행을 읽고 버리는 대신 조건으로 위치를 찾습니다. 17일 동점 행인 17번도 빠짐없이 나옵니다. 다음 호출은 방금 받은 마지막 행 (2026-09-07, 8) 을 넘깁니다.

sql
WITH cur AS (SELECT DATE '2026-09-07' AS last_created, 8 AS last_id)
SELECT
       b.id
     , b.title
     , b.created_at
  FROM board b
 CROSS JOIN cur
 WHERE b.created_at < cur.last_created
    OR (b.created_at = cur.last_created AND b.id < cur.last_id)
 ORDER BY b.created_at DESC, b.id DESC
 FETCH FIRST 10 ROWS ONLY;
text
ID | TITLE     | CREATED_AT
---+-----------+-----------
7  | 게시글 07 | 2026-09-07
6  | 게시글 06 | 2026-09-06
5  | 게시글 05 | 2026-09-05
4  | 게시글 04 | 2026-09-04
3  | 게시글 03 | 2026-09-03
2  | 게시글 02 | 2026-09-02
1  | 게시글 01 | 2026-09-01
(7행)

7행만 돌아왔으니 마지막입니다. 10행을 요청해 10행보다 적게 오면 다음 페이지가 없다는 신호입니다.

이전 페이지로 가려면 부등호와 정렬 방향을 모두 뒤집어 읽고 화면에서 순서를 뒤집습니다. 소스 파일의 세 번째 쿼리가 그 예입니다. 페이지 2 의 첫 행 (2026-09-17, 17) 보다 앞선 3 행을 오름차순으로 읽으면 18번, 19번, 20번이 나옵니다.

응용 변형 예제
  • 예제 5: Oracle ROWNUM
  • 예제 6: ROW_NUMBER 방식
  • 예제 7: MySQL LIMIT 와 MSSQL OFFSET FETCH·TOP
  • 예제 8: 키셋 페이징
이전 섹션3 코드 예제4 / 6다음 섹션5 자주 하는 실수 (Tip)