데이터베이스 페이징은 큰 데이터셋을 작은 페이지 단위로 나누어 필요한 만큼만 가져오는 기술입니다. 대량의 데이터를 효율적으로 처리하고 클라이언트와 서버의 리소스 사용을 최적화하기 위해 사용됩니다.
클라이언트는 페이지 번호와 페이지당 표시할 데이터 수를 요청합니다.
예: GET /api/books?page=2&size=10
page=2: 2번째 페이지size=10: 한 페이지에 10개의 데이터 표시서버는 데이터베이스에서 요청받은 페이지에 해당하는 데이터만 가져옵니다.
SQL의 LIMIT와 OFFSET을 사용하여 원하는 데이터만 쿼리합니다.
SELECT * FROM books
LIMIT 10 OFFSET 10;
LIMIT: 가져올 데이터 개수OFFSET: 건너뛸 데이터 개수 (페이지 번호에 따라 달라짐)page=2, size=10이면 OFFSET은 (2-1) × 10 = 10const getPagedBooks = (req, res) => {
let { page = 1, size = 10 } = req.query; // 기본값 설정
page = parseInt(page);
size = parseInt(size);
const offset = (page - 1) * size;
const sql = `SELECT * FROM books LIMIT ? OFFSET ?`;
conn.query(sql, [size, offset], (err, results) => {
if (err) {
return res.status(500).json({ error: "Database error" });
}
res.status(200).json(results);
});
};
GET /api/books?page=2&size=10SELECT * FROM books LIMIT 10 OFFSET 10;페이징 결과와 함께 전체 데이터 개수를 반환하면 클라이언트에서 총 페이지 수를 계산할 수 있습니다:
const sqlCount = `SELECT COUNT(*) as total FROM books`;
const sqlPaged = `SELECT * FROM books LIMIT ? OFFSET ?`;
conn.query(sqlCount, (err, countResults) => {
if (err) return res.status(500).json({ error: "Database error" });
const total = countResults[0].total;
const totalPages = Math.ceil(total / size);
conn.query(sqlPaged, [size, offset], (err, results) => {
if (err) return res.status(500).json({ error: "Database error" });
res.status(200).json({
data: results,
total,
totalPages,
currentPage: page,
});
});
});
OFFSET 계산 비용이 커질 수 있음.Keyset Pagination은 OFFSET 대신 정렬된 특정 키(예: id)를 기반으로 페이지를 가져오는 방식입니다.
sql
코드 복사
SELECT * FROM books
WHERE id > ?
ORDER BY id ASC
LIMIT 10;
id를 클라이언트가 저장하고, 다음 요청에 이를 전달하여 페이지를 가져옵니다.const conn = require("../db");
const { StatusCodes } = require("http-status-codes");
// (카테고리별 , 신간 여부) 전체 도서 목록 조회
const allBooks = (req, res) => {
let { category_id, newBook, limit, currentPage } = req.query;
// limit : page당 보여줄 갯 수
// currentPage : 현재 페이지(숫자)
// offset : (current-1)*limit
let offset = limit * (currentPage - 1);
let sql = `SELECT * FROM books `;
let values = [];
if (category_id && newBook) {
// category별 신간
sql += ` WHERE category_id = ? AND pub_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 1 month) AND NOW()`;
values = [category_id];
} else if (category_id) {
// category별
sql += ` WHERE category_id = ? `;
values = [category_id];
} else if (newBook) {
// 신간
sql +=
" WHERE pub_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 1 month) AND NOW()";
}
sql += ` LIMIT ? OFFSET ?`;
values.push(parseInt(limit), offset);
conn.query(sql, values, (err, results) => {
if (err) {
console.error(err);
return res
.status(StatusCodes.BAD_GATEWAY)
.json({ error: "데이터베이스 오류입니다." });
}
if (results.length) {
return res.status(StatusCodes.OK).json(results);
} else {
return res.status(StatusCodes.NOT_FOUND).end();
}
});
};
module.exports = { allBooks, bookDetail };
let sql은 SELECT * FROM books으로 설정values라는 빈 배열 선언if...else...else if()문을 사용해서 request의 쿼리값에 따라 동적으로 WHERE조건을 values배열에 추가sql += 'LIMIT ? OFFSET ?' 으로 데이터베이스에 실행할 쿼리문 최종 추가values.push(parseInt(limit),offset);으로 LIMIT ? OFFSET ?에 사용할 조건도 추가conn.query()실행쿼리문은 띄워쓰기를 잘해야합니다. let sql구문을 보면 끝에 공백 추가를 해야 질의가 들어갑니다.sql += ' LIMIT ? OFFSET ? ';도 마찬가지고 앞에 공백 추가를 해야 합니다.문법적으로 어렵지는 않습니다. 다만 논리적이지 못할 경우 워하는 값을 얻어내기 힘듭니다.LIMIT, OFFSET은 질의문 마지막에 들어가야하는데 어떻게 if문을 분기처리해야하지?category_id,newBook이라는 값이 있거나 없거나 하는 상황에 따라 어떻게 원하는 값을 얻어낼 수 있지?