Сайт о телевидении

Сайт о телевидении

» » Скрипт постраничной навигации на PHP. Постраничная навигация без использования COUNT и LIMIT

Скрипт постраничной навигации на PHP. Постраничная навигация без использования COUNT и LIMIT

Данный скрипт позволит вывести не только стрелки на «предыдущую страницу» и «следующие страницу», как в статье , но и, при желании, поможет вывести последовательный список ссылок на все страницы сайта. Как это выглядит, вы можете увидеть на моем сайте – просто спуститесь вниз на главной странице сайта.

Посредством phpmyadmin (или sql) создайте таблицу (название на ваше усмотрение, я назвал opt) с двумя полями. Первое поле назовите id , второе str . Оба поля должны иметь числовой тип (int), полю id назначьте auto_increment . Данная таблица будет хранить числовое значение (в поле str), определяющее число постов выводимых на одной странице.

Далее все просто. Делаем запрос и извлекаем это числовое значение, заносим его в переменную, например, $num . Определяем общее число постов (сообщений) в базе данных, заносим это значение в переменную $posts . Находим общее число страниц сайта ($total) и округляем в меньшую сторону. Вычисляем, с какого поста необходимо выводить сообщения (посты) на данной странице ($start). Делаем запрос к таблице с постами и ограничиваем по лимиту ($num), также указываем параметр ($start), с которого начнется вывод записей. Теперь остается соответствующим образом расположить ссылки на соответствующие страницы и, вуаля, готова. Более подробное описание скрипта можно увидеть непосредственно в коде.

$result77 = mysql_query("SELECT str FROM opt", $db); $myrow77 = mysql_fetch_array($result77); // число постов на одной странице $num = $myrow77["str"]; // Извлекаем из URL текущую страницу @$page = $_GET["page"]; // Определяем общее число сообщений в базе данных $result00 = mysql_query("SELECT COUNT(*) FROM posti"); $temp = mysql_fetch_array($result00); $posts = $temp; // Находим общее число страниц $total = (($posts - 1) / $num) + 1; $total = intval($total); // округляем текущую страницу $page = intval($page); // Если переменная $page меньше 0 или пуста // присваиваем $page 1 // А если значение $page выходит за $total, // присваиваем $page значение переменной $total if(empty($page) or $page < 0) $page = 1; if($page > $total) $page = $total; // Вычисляем начиная с какого номера // следует выводить сообщения $start = $page * $num - $num; // Выбираем $num сообщений начиная с номера $start // -----МИНИ ПОСТЫ----- $result = mysql_query("SELECT * FROM posti ORDER BY date DESC LIMIT $start, $num ",$db);

//Выводим все посты в цикле

// Проверяем нужна ли стрелки назад if ($page != 1) $pervpage = "Первая Предыдущая"; // Проверяем нужны ли стрелки вперед if ($page != $total) $nextpage = "Следующая Последняя"; // Находим две ближайшие станицы с обоих краев, если они есть if($page - 2 > 0) $page2left = " ". ($page - 2) ." "; if($page - 1 > 0) $page1left = "". ($page - 1) ." "; if($page + 2 <=$total) $page2right = " ". ($page + 2) .""; if($page + 1 <=$total) $page1right = " ". ($page + 1) .""; // выводим ссылки echo $pervpage.$page2left.$page1left."".$page."".$page1right.$page2right.$nextpage;

Плюс данной навигации в том, что все страницы сайта можно расположить в пределах 3 кликов от главной страницы. Данная особенность может пригодиться, например, при работе с сервисом SAPE.

Проблема реализации постраничной навигации часто встает перед начинающими PHP-программистами. К разбиению объёмного текста на отдельные страницы прибегают во многих Web-приложениях от гостевых книг и форумов до различных каталогов. Давайте
решим эту проблему.
Итак, что нам требуется для реализации постраничной навигации? Для примера возьмем гостевую книгу, содержащую несколько сотен сообщений, в которой требуется выводить на страницу Х сообщений.
Рассмотрим задачу более конкретно. Сообщения пользователей хранятся в базе данных post со следующей структурой:

  • id – номер сообщения,
  • text – тело сообщения,
  • name – имя автора,
  • time – время создания.

В итоге нам надо получить вывод Х сообщений на страницу, а также организовать удобную навигацию, например, такую:

<< < ..2|3|4|5|6.. > >>

где 4 - текущая станица. Для того чтобы отслеживать текущую страницу, будем использовать параметр page , передаваемый через URL. К примеру:

Www.myserver.com/index.php?page=X.

Здесь X - номер станицы (для примера, пусть Х будет равно 25).

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

// Устанавливаем соединение с базой данных
include "config.php" ;
// Переменная хранит число сообщений выводимых на станице
$num = 25 ;
// Извлекаем из URL текущую страницу
$page = $_GET [ "page" ];
// Определяем общее число сообщений в базе данных
$result = mysql_query ("SELECT COUNT(*) FROM post" );
$posts = mysql_result ($result , 0 );
// Находим общее число страниц
$total = intval (($posts - 1 ) / $num ) + 1 ;
// Определяем начало сообщений для текущей страницы
$page = intval ($page );
// Если значение $page меньше единицы или отрицательно
// переходим на первую страницу
// А если слишком большое, то переходим на последнюю
if(empty($page ) or $page < 0 ) $page = 1 ;
if($page > $total ) $page = $total ;
// Вычисляем начиная к какого номера
// следует выводить сообщения
$start = $page * $num - $num ;
// Выбираем $num сообщений начиная с номера $start
$result = mysql_query ("SELECT * FROM post LIMIT $start , $num " );
// В цикле переносим результаты запроса в массив $postrow
while ($postrow = mysql_fetch_array ($result ))
?>

На этом первая часть закончена. Двумерный массив postrow хранит все поля таблицы post, необходимые для отображения станицы. Вот пример как можно организовать вывод сообщений.

echo "

" ;
for($i = 0 ; $i < $num ; $i ++)
{
echo "


" ;
}
echo "
" . $postrow [ $i ][ "name" ]. " " . $postrow [ $i ][ "time" ]. "
" . $postrow [ $i ][ "text" ]. "
" ;
?>

Теперь надо организовать навигацию. Формируем составляющие будущей навигации.
Коды стрелки «в начало» и «назад» поместим в одну переменную, также поступим и со стрелками «в конец» и «вперед».

// Проверяем нужны ли стрелки назад
if ($page != 1 ) $pervpage = "<<
. ($page - 1 ) . ">< " ;
// Проверяем нужны ли стрелки вперед
if ($page != $total ) $nextpage = " ">>
. $total . ">>>" ;

// Находим две ближайшие станицы с обоих краев, если они есть
if($page - 2 > 0 ) $page2left = " " . ($page - 2 ) . " | " ;
if($page - 1 > 0 ) $page1left = "" . ($page - 1 ) . " | " ;
if($page + 2 <= $total ) $page2right = " | " . ($page + 2 ) . "" ;
if($page + 1 <= $total ) $page1right = " | " . ($page + 1 ) . "" ;

// Вывод меню
echo $pervpage . $page2left . $page1left . "" . $page . "" . $page1right . $page2right . $nextpage ;

друже 5 апреля 2013 в 21:12

Постраничная навигация без использования COUNT и LIMIT

  • Чулан *

Все, кто сталкивался с созданием постраничной навигации с использованием связки mySQL + PHP наверняка замечали, что выборка начинает замедляться при просмотре таблиц со множеством полей (от 100000). В интернете я толком ничего не нашел по этой теме, везде предлагаются классические схемы на основе использования COUNT + LIMIT. Статья рассчитана на тех, кто уже знаком с mySQL и PHP.
Предлагаю несколько иной подход, который даст возможность просматривать страницы с практически любым количеством полей, и тем самым мы попробуем решить проблему «тяжелых» таблиц.

Вместо COUNT мы будем использовать SELECT.
Первое, что нужно сделать - создать столбец с AUTO_INCREMENT и поставить на него Primary Key . Я его для примера назвал id . Это даст нам возможность обойти COUNT и обратиться непосредственно к значению столбца, а именно:

$msg=20; //20 сообщений на страницу $page=(int)$_GET["page"]; //get запрос заносим в $page $q=mysql_query("SELECT max(id) FROM table_name"); //узнаем максимальное значение столбца $str=mysql_result($q,0); //результат в $str $total=(int)(($str - 1) / $msg) + 1; //узнаем общее количество страниц для отображения в навигации $start=$page * $msg - $msg; //здесь узнаем с какой записи начинаем вывод сообщений $stop=$page * $msg; //здесь собсно до какой выводим

Далее рассмотрим как использовать выборку без LIMIT

$q=mysql_query("SELECT * FROM user WHERE id > $start AND id < $stop"); //вот так всё просто while ($body=mysql_fetch_array($q)) //собсно заносим результат запроса в массив echo "

"; for($i=0; $i < $msg; $i++) //этот цикл выводит сообщения в диапазоне id > $start и id < $stop { echo ""; } echo "
".$body[$i]["row_name"]."".$body[$i]["row_name"]."".$body[$i]["row_name"]."
";

Ну а дальше осталось добавить стрелки навигации, но это думаю вы уже сами сделаете ибо тут вариантов в интернете море. Как видите ничего сложного, единственное я не проверял как поведет себя такой прием при удалении поля из таблицы. Но в этом случае можно просто создать дополнительную таблицу, где будет находится импровизированный счетчик, который будет содержать значение количества полей в основной таблице и при удалении или дополнении будет соответственно изменяться, реализовать это можно с помощью UPDATE.

В общем надеюсь кому-нибудь пригодится, я сам очень долго искал как решить эту проблему. Да, забыл сказать, что здесь говорилось в расчет на использование InnoDB. Вот несколько сравнительных тестов:

При подсчете времени выполнения запроса LIMIT среднее значение 0.09 с. в таблице 1000000 записей
SELECT * FROM table_name LIMIT 999000, 20

При подсчете времени выполнения запроса без LIMIT среднее значение 0.0005 с. в таблице 1000000 записей
SELECT * FROM table_name WHERE id > 999000 AND id < 999020

При подсчете времени выполнения запроса COUNT(*) среднее значение 0.06 с. в таблице 1000000 записей
SELECT COUNT(*) FROM table_name

При подсчете времени выполнения запроса без COUNT(*) среднее значение 0.0003 с. в таблице 1000000 записей
SELECT COUNT(*) FROM table_name

Теги: mysql, php, pagination, LIMIT,COUNT, постраничная навигация

/* 09.07.2008 */

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

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

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

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

Поехали...

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

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

SELECT * FROM table1

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

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

Синтаксис оператора LIMIT: LIMIT row_count

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

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

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

#запрос для выборки страницы 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, то можно сказать, что он идет в конце строки запроса (после него идут операторы довольно редко используемые).

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

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

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

страница №3

При клике по этой ссылке запускается скрипт 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 .

//подключаем класс 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()."

"; //а также строку с номерами страниц (основные ярлыки) 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:

  • Ключевое слово SQL_CALC_FOUND_ROWS должно указываться в первом операторе SELECT ;
  • Значение FOUND_ROWS() будет точным только при условии применения UNION ALL . Если указано UNION без ALL , происходит исключение дубликатов, и значение FOUND_ROWS() будет лишь приблизительным;
  • Если в UNION не присутствует LIMIT , то SQL_CALC_FOUND_ROWS игнорируется и возвращается количество строк во временной таблице, которая создается для выполнения UNION .