Постраничный вывод
В таблице могут лежать тысячи записей, но на странице показывают десять-двадцать. Остальные доступны по ссылкам «2», «3», «Далее». Для этого из базы запрашивают не всё, а одну порцию.
Примеры используют базу SQLite в памяти и здесь не запускаются — выполните их у себя.
LIMIT и OFFSET
LIMIT задаёт, сколько строк вернуть, OFFSET — сколько пропустить:
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT)');
for ($i = 1; $i <= 25; $i++) {
$pdo->exec("INSERT INTO posts (title) VALUES ('Статья $i')");
}
$titles = $pdo->query('SELECT title FROM posts ORDER BY id LIMIT 3 OFFSET 0')
->fetchAll(PDO::FETCH_COLUMN);
echo implode(', ', $titles); // Статья 1, Статья 2, Статья 3Смещение по номеру страницы
Чтобы получить нужную страницу, смещение считают по формуле: номер страницы минус один, умноженный на размер страницы:
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT)');
for ($i = 1; $i <= 25; $i++) {
$pdo->exec("INSERT INTO posts (title) VALUES ('Статья $i')");
}
$page = 3;
$perPage = 10;
$offset = ($page - 1) * $perPage;
$statement = $pdo->prepare('SELECT title FROM posts ORDER BY id LIMIT :limit OFFSET :offset');
$statement->bindValue('limit', $perPage, PDO::PARAM_INT);
$statement->bindValue('offset', $offset, PDO::PARAM_INT);
$statement->execute();
echo implode(', ', $statement->fetchAll(PDO::FETCH_COLUMN));На третьей странице окажутся статьи с 21-й по 25-ю.
Сколько всего страниц
Общее число записей делят на размер страницы и округляют вверх:
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT)');
for ($i = 1; $i <= 25; $i++) {
$pdo->exec("INSERT INTO posts (title) VALUES ('Статья $i')");
}
$total = (int) $pdo->query('SELECT COUNT(*) FROM posts')->fetchColumn();
$pages = (int) ceil($total / 10);
echo "Записей: $total, страниц: $pages"; // Записей: 25, страниц: 3Номер страницы из адреса
Номер приходит в адресе: /posts.php?page=2. Его приводят к числу и ограничивают:
<?php
// posts.php
$page = max(1, (int) ($_GET['page'] ?? 1));
$page = min($page, $pages);Теперь page=abc, page=-5 и page=9999 не сломают запрос.
Ссылки
<?php
function pageLinks(int $current, int $pages): string
{
$links = [];
for ($page = 1; $page <= $pages; $page++) {
$links[] = $page === $current
? "<b>$page</b>"
: "<a href=\"?page=$page\">$page</a>";
}
return implode(' ', $links);
}
echo pageLinks(2, 3);Текущая страница выводится жирным текстом, остальные — ссылками.
Большие таблицы
При большом смещении база всё равно читает пропускаемые строки: OFFSET 100000 работает медленно. Для длинных лент используют другой приём — запоминают идентификатор последней показанной записи и просят следующие за ней:
SELECT id, title FROM posts WHERE id > 120 ORDER BY id LIMIT 10;Так устроена кнопка «Показать ещё».
Частая ошибка
В MySQL значения, переданные через execute([$perPage, $offset]), подставляются как строки, и запрос LIMIT '10' заканчивается ошибкой. Для LIMIT и OFFSET используйте bindValue() с PDO::PARAM_INT. И всегда указывайте ORDER BY: без сортировки порядок строк не определён, и запись может попасть на две страницы или не попасть ни на одну.