소스: sql-src/work_01_paging/01_offset_paging.sql. 게시판 표 board(id, title, writer, created_at, views)에 27행이 있습니다. 게시글 01~27 이 날짜순으로 쌓여 있고 같은 날짜가 여러 개 있습니다. 페이지 크기는 10 이라 페이지가 3 개입니다.
같은 created_at 을 가진 행이 있는지 먼저 봅니다.
SELECT
created_at
, COUNT(*) AS cnt
FROM board
GROUP BY created_at
HAVING COUNT(*) > 1
ORDER BY created_at;CREATED_AT | CNT
-----------+----
2026-09-07 | 2
2026-09-17 | 2
2026-09-24 | 3
(3행)9월 7일 2건(7·8번), 17일 2건(17·18번), 24일 3건(24·25·26번)이 동점입니다. 내림차순으로 세면 10번째가 18번, 11번째가 17번이라 페이지 1 과 2 의 경계에 17일 동점이 걸쳐 있습니다. 20번째와 21번째(8번, 7번)도 7일 동점입니다. created_at 만으로 정렬하면 이 경계 행들의 순서가 호출마다 달라질 수 있습니다.
id 를 덧붙인 정렬로 첫 페이지를 가져오면서 총 건수와 총 페이지 수를 함께 받습니다.
SELECT
id
, title
, created_at
, COUNT(*) OVER () AS total
, CEIL(COUNT(*) OVER () / 10.0) AS total_pages
FROM board
ORDER BY created_at DESC, id DESC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;ID | TITLE | CREATED_AT | TOTAL | TOTAL_PAGES
---+-----------+------------+-------+------------
27 | 게시글 27 | 2026-09-26 | 27 | 3
26 | 게시글 26 | 2026-09-24 | 27 | 3
25 | 게시글 25 | 2026-09-24 | 27 | 3
24 | 게시글 24 | 2026-09-24 | 27 | 3
23 | 게시글 23 | 2026-09-23 | 27 | 3
22 | 게시글 22 | 2026-09-22 | 27 | 3
21 | 게시글 21 | 2026-09-21 | 27 | 3
20 | 게시글 20 | 2026-09-20 | 27 | 3
19 | 게시글 19 | 2026-09-19 | 27 | 3
18 | 게시글 18 | 2026-09-17 | 27 | 3
(10행)total 은 10행 모두 27 입니다. 분석 함수는 OFFSET·FETCH 로 자르기 전의 전체 행에 적용되기 때문입니다. total_pages 는 27 / 10.0 을 올림한 3 입니다. 10.0 으로 나누는 이유는 MSSQL 처럼 정수끼리 나누면 소수점이 잘리는 DB 를 위해서입니다.
페이지 2 는 OFFSET 10 이고 소스에 있습니다. 결과는 예제 6 과 같은 10 행(17번부터 8번)이라 생략하고, 마지막 페이지인 3 만 보입니다.
SELECT
id
, title
, created_at
, COUNT(*) OVER () AS total
FROM board
ORDER BY created_at DESC, id DESC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;ID | TITLE | CREATED_AT | TOTAL
---+-----------+------------+------
7 | 게시글 07 | 2026-09-07 | 27
6 | 게시글 06 | 2026-09-06 | 27
5 | 게시글 05 | 2026-09-05 | 27
4 | 게시글 04 | 2026-09-04 | 27
3 | 게시글 03 | 2026-09-03 | 27
2 | 게시글 02 | 2026-09-02 | 27
1 | 게시글 01 | 2026-09-01 | 27
(7행)마지막 페이지는 10 행이 안 되는 7 행이 돌아옵니다. 페이지 1 의 끝 18번(17일)과 페이지 2 의 시작 17번(17일), 페이지 2 의 끝 8번(7일)과 페이지 3 의 시작 7번(7일)이 id 내림차순으로 이어져 겹치거나 빠진 행이 없습니다.
SELECT
id
, title
, COUNT(*) OVER () AS total
FROM board
ORDER BY created_at DESC, id DESC
OFFSET 30 ROWS FETCH NEXT 10 ROWS ONLY;ID | TITLE | TOTAL
---+-------+------
(0행)27 행을 넘어선 곳부터 읽으면 0행이라 total 도 받을 수 없습니다. 사용자가 주소창에 큰 페이지 번호를 넣거나, 삭제로 마지막 페이지가 사라진 경우입니다. 총 건수를 이 쿼리에서만 얻으면 그 경우에 "총 0건" 으로 잘못 보이므로, 서버에서 페이지 번호를 총 페이지 수로 자르거나 총 건수를 별도 COUNT(*) 로 받습니다.
팁총 건수
COUNT는 대용량 표에서 비싼 연산입니다. 화면에 "총 3,201,554건" 이 꼭 필요하지 않다면 "더 보기" 버튼으로 다음 페이지가 있는지(FETCH를 한 건 더 요청) 만 확인하거나 근사치를 씁니다.