https://myzoodom.ru/


/* 09.07.2008 */

Постраничный вывод (PHP и MySQL)

Автор: Цыгырлаш Игорь (09.07.2008)

Довольно часто на сайте возникает необходимость в отображении большого объема однотипной информации, при этом, для удобства восприятия, её следует разбивать на части, т.е. реализовать постраничный просмотр этой информации. Данное решение используется поисковыми системами при отображении результатов поиска, форумами, досками объявлений и т.д. Данная статья описывает то, как используя MySQL и PHP реализовать постраничный вывод.

Для начала отмечу, что статья не учит работе с базой данных и PHP, а даёт объяснение реализации, и приводит готовый к использованию PHP класс для постраничной разбивки (постраничной навигации).

Начнем! Допустим имеется база данных (MySQL), например, с объявлениями. Нам нужно реализовать их отображение на сайте, порциями по 20 штук на странице. Для перехода же между порциями в нижней части каждой страницы необходимо сформировать ссылки с номерами «порций» (ярлыки страниц):

Пример постраничной разбивки

Поехали ...

Выборка данных порциями

Для выборки ВСЕХ объявлений из базы требуется запрос вида:

SELECT * FROM table1

Конечно, это упрощенный вариант, и в реальных задачах, чаще всего, в запросе присутствуют различные условия (операторы WHERE, ORDER BY ...).

Для того чтобы этот запрос делал выборки порциями, необходимо к нему добавить оператор LIMIT:

Синтаксис оператора LIMIT:
LIMIT [offset,] row_count

Необязательный параметр offset сообщает сколько рядов от начала выборки нужно пропустить, а row_count указывает сколько рядов нужно выбрать, т.е. LIMIT 0, 20 (или просто LIMIT 20 опустив нулевой offset) выбирает первые 20 рядов (с 0 по 19 ряд), а LIMIT 40, 20 указывает пропустить 40 (с 0 по 39 ряд) и выбрать следующие 20 (т.е. будут выбраны ряды с номера 40 по 59).

Иллюстрация оператора LIMIT

Обращаю ваше внимание, что ряды в выборке нумеруются с нуля, а не с единицы.

Таким образом запросы для нашего примера с объявлениями будут следующими:

#запрос для выборки страницы 1:
SELECT * FROM table1 LIMIT 0, 20

#запрос для выборки страницы 2:
SELECT * FROM table1 LIMIT 20, 20

#запрос для выборки страницы 3:
SELECT * FROM table1 LIMIT 40, 20

и .т.д. offset увеличиваем на 20 для каждой следующей страницы, а row_count всегда равен 20.

Ещё необходимо отметить, что оператор LIMIT в запросе идет по порядку после WHERE, GROUP BY, HAVING, ORDER BY, но если вы новичок в MySQL, то можно сказать, что он идет в конце строки запроса (после него идут операторы довольно редко используемые).

Вторая часть с которой нам нужно разобраться это строка с ярлыками страниц...

Ярлыки страниц

Ярлыки — это ссылки для «перелистывания» страниц, т.е. ссылки нажимая на которые пользователь получает очередную порцию данных. Эти ссылки содержат в качестве параметра номер нужной порции (страницы).

Например, для выборки третьей двадцатки объявлений ярлык может иметь следующий вид:

<a href="obyavleniya.php?page_number=3"> страница №3 </a>

При клике по этой ссылке запускается скрипт obyavleniya.php, которому доступен параметр page_number, сообщающий, что запрашивается 3 двадцатка объявлений — 3 страница. Скрипт пропускает первые 40 объявлений, и выбирает следующие 20.

Для вывода этой строки ярлыков требуется знать общее число страниц (чтобы знать сколько ярлыков "рисовать"). Его мы можем получить, разделив общее число объявлений на количество объявлений на странице, округлив результат до большего целого. Т.е., если в нашем примере, допустим, всего 107 объявлений, а выводим мы их на каждой странице по 20 штук, то число страниц будет: 107 / 20 = 5.35, т.е. 5 полных страниц (по 20 объявлений) + одна неполная (7 объявлений), итого, округлив получаем 6 страниц (соответственно будет 6 ярлыков).

Для подсчета общего числа объявлений, есть два пути. Первый путь — выполнить отдельный суммирующий запрос практически аналогичного запросу для выборки данных, только без ограничивающего оператора LIMIT, и ненужных операций сортировки (ORDER BY), например:

#запрос для выборки объявлений 3 страницы
SELECT * FROM table1 WHERE category_id="89" AND ... ORDER BY publish_date DESC LIMIT 40, 20
#запрос для подсчета ВСЕХ объявлений в базе
SELECT COUNT(*) FROM table1 WHERE  category_id="89"  AND ...

Первый запрос выполняет выборку объявлений, а второй, подсчитывает их общее число функцией COUNT. На практике запросы выборки данных бывают весьма громоздкими и тяжелыми, поэтому дополнительный тяжелый запрос для подсчета не самая «необходимая» операция. Так же этот путь не такой изящный как второй...

В MySQL 4.0.0 появились замечательные вещи, такие как функция FOUND_ROWS и связанная с ней SQL_CALC_FOUND_ROWS - опция оператора SELECT.

Рассмотрим второй вариант подсчета общего числа рядов:

SELECT SQL_CALC_FOUND_ROWS * FROM table1 WHERE category_id="89" AND ... ORDER BY publish_date DESC LIMIT 40, 20
SELECT FOUND_ROWS()

Опять же, первый запрос делает выборку объявлений, а второй получает их общее число, но ...

Запрос выборки объявлений в данном случае отличается от выборки из первого варианта только наличием опции SQL_CALC_FOUND_ROWS. Данная опция указывает MySQL вместе с выборкой данных сделать и подсчёт всех тех строк которые бы вернул запрос без оператора LIMIT. Т.е. по сути данный запрос включает в себя в скрытом виде COUNT запрос из первого варианта. При этом сама подсчитанная сумма не возвращается, а запоминается сервером. Теперь, для того чтобы узнать это число, нужно выполнить запрос с функцией FOUND_ROWS (при этом сервер не производит никаких вычислений, просто отдает то, что запомнил раньше).

Второй вариант однозначно выглядит более элегантным, а также может обеспечить некоторый выигрыш по скорости.

Собираем все вместе

Теперь вы знаете все что нужно, и я могу привести алгоритм описывающий логику скрипта obyavleniya.php для постраничной разбивки, который запускается при заходе пользователя на страницу объявлений...

  1. первым делом при запуске скрипта смотрим какую страницу запрашивает пользователь (в нашем примере на это указывает параметр page_number);
  2. на основании номера запрашиваемой страницы вычисляем параметр offset оператора LIMIT;
  3. запускаем запрос выборки объявлений с оператором LIMIT offset, 20 (где, 20 — это количество отображаемых объявлений на странице в нашем примере);
  4. получаем общее число объявлений в базе;
  5. на основании пункта 4 вычисляем общее число страниц объявлений и формируем строку ярлыков.

Вот собственно и всё. Теперь, я надеюсь, вы сможете написать свой скрипт, или воспользоваться моим, понимая суть того как он работает.

PHP класс Paging для постраничной разбивки

Теперь я приведу пример как организуется постраничная навигация с использованием PHP-класса Paging.

<?php
//подключаем класс Paging
require('paging.inc.php');

//соединяемся с базой данных
$_DB = new mysqli($host,$user,$passwd,$db_name);

//создаем экземпляр класса Paging
//в качестве параметра передаем ему указатель на соединение с MySQL
$_PAGING = new Paging($_DB);

//выполняем обычный запрос данных не заботясь 
//о разбивке на страницы через метод get_page объекта класса Paging
$r = $_PAGING->get_page( 'SELECT * FROM table1' ); 

while($row = $r->fetch_assoc())
{
//обрабатываем КАК ОБЫЧНО полученные из базы данные и выводим пользователю
}

//выводим информационную строку вида: "Показано с 1 по 20 из 107" 
echo $_PAGING->get_result_text().' объявлений';

//выводим ссылки-ярлыки на предыдущую и следующую страницы
echo 'Страницы: '.$_PAGING->get_prev_page_link().' '.$_PAGING->get_next_page_link().'<br /><br />';

//а также строку с номерами страниц (основные ярлыки)
echo $_PAGING->get_page_links();

?>

Единственное чем данный скрипт отличается от обычного скрипта без постраничной разбивки, так это тем, что запрос выборки данных которые нужно разделить на части производится не через mysqli->query(), а через метод get_page() реализованный в классе Paging, а так же тремя последними строками которые отображают ярлыки и строку отчет о выборке.

Постскриптум

P.S.: Этот постскриптум я привожу скорее для полноты изложения, нежели как реально актуальную информацию для большинства читателей.

Применение SQL_CALC_FOUND_ROWS и FOUND_ROWS() имеет некоторые подводные камни при использовании в UNION-запросах, так как операторы LIMIT могут использоваться в нескольких местах, и могут касаться как отдельных операторов SELECT в составе UNION, так и общего результата UNION в целом. Цель же SQL_CALC_FOUND_ROWS для UNION состоит в подсчёте количества строк, которые будут возвращены без глобального LIMIT. Поэтому следует привести условия применения SQL_CALC_FOUND_ROWS с запросами UNION:

-> Обсудить статью в форуме
https://myzoodom.ru/
<- Назад к списку статей
•> CSS прозрачность (css opacity, javascript opacity)