постраничная навигация php mysql
Постраничная навигация на PHP
Часто при разработке и выводе контента появляется необходимость использования постраничной навигации. Кто-то скорее всего использует готовые решения от своего фреймворка. Кто-то, возможно, не заморачивается и лупит страницы просто циклом. У кого-то есть свои наработки в этом направлении. Вот я как раз и хочу поделиться своим решением данной задачи.
Существует множество вариаций расположения и отображения кнопок, лично я пришел к следующему решению, которое по моему мнению наиболее наглядно и удобно. Подходит как для 5 страниц так и для 5000.
Пример HTML кода
Не буду ходить вокруг да около, сразу приложу пример сформированного скриптом html кода:
Логика построения
По настройкам параметров я остановлюсь чуть позже после приведения кода, сейчас опишу логику формирования самих номеров.
С кнопками «Назад» и «Вперед» думаю все понятно, к тому же их можно просто отключить, поэтому на них не буду заострять внимания.
Первый и последний номер страницы отображается всегда, своего рода кнопки «В начало» и «В конец».
Середина формируется уже по простому алгоритму. Отображается просматриваемая страница и по N страниц по бокам. На примере отображается по N=3 страницы. В принципе все просто и понятно, но особая хитрость используется при приближении к краям. Опишу на примерах:
Страница 1-3 (где 3 = N)
Отображаются первые N*2 страниц и последняя.
Отображается первая и дальше сформированная строка от 4-3=1 до 4+3=7. Первая страница зарезервирована поэтому формируются номера от 2 до 7.
Пожалуй во всех навигациях что я видел (включая хабр) строка была бы сформирована с пропуском, т.е. 1… 3 4 5 6 7 8 9… 17
Но ведь это не логично, отображать многоточие вместо одного числа. При построении второго многоточия выполняется аналогичная проверка.
Середина уже стандартна.
Формирование окончания аналогично началу
Редиректы
Помимо этого из особенностей хочу выделить еще 2 момента, это проверка существования страницы и редирект на «правильный» адрес. Т.е. к примеру, тут же на хабре первая страница может быть доступна сразу по 2м адресам:
habrahabr.ru/sandbox/page1
habrahabr.ru/sandbox
Скрипт не дает зайти на адрес page/1/ и выполняет редирект на «чистый» адрес
Так же если указан слишком большой номер страницы будет выполнен редирект на последнюю существующую. К примеру были удалены материалы или изменено количество записей на страницу. Не могу правда однозначно сказать полезно ли это будет с точки зрения СЕО, но для пользователей мне кажется так будет удобнее.
PHP код и его использование
Для наглядности, приведу пример построения навигации песочницы:
habrahabr.ru/sandbox/page12
Или же если номер страницы прописан внутри URL:
example.com/some_url/1.html — первая страница
example.com/some_url/1-page2.html — вторая страница
$limit — количество записей на страницу
$count_all — общее количество записей
$page_num — номер страницы на которой находится пользователь
На этом, пожалуй, всё. Буду рад любой конструктивной критике.
PS. Огромное спасибо всем отписавшимся, особенно тем кто ругает (и правильно делает).
Обещаю со всем ознакомиться, принять во внимание и исправиться.
Постраничная навигация с MySQL при большом количестве записей
Рано или поздно многие крупные проекты сталкиваются с проблемами производительности при постраничной навигации по записям. Некоторые из них решают эту проблему ограничением количества доступных для просмотра записей (скажем, не больше 1000). Вполне приемлемое решение. Но в этом случаем могут возникнуть проблемы с индексированием сайта сторонними поисковиками, которые и представляют наибольшую угрозу. В этой статье я хотел бы отказаться от привычной для всех панели навигации вида «1..2..3..4..» в пользу простой «вперед… назад» (будет проще объяснить), но это не проблема реализовать подобное и с первым вариантом.
Более точно определить тему, назвав, какое количество записей считать достаточно большим для появления тормозов, не получится, так как эта цифра для всех разная и сильно зависит от того, насколько быстрые у Вас жесткие диски, сколько памяти, и какая часть Ваших данных уже закеширована в ней и тд. Но если Вы и Ваши сервера ощущают, что n-ная страница при выводе даётся тяжелее первой, и при этом не знаете, что с этим делать – статья для Вас. Но для начала, я хотел бы на пальцах объяснить, почему ОНО работает медленно.
Кстати, тест происходит на виртуальной машинке, работаю я с СУБД под рутом, версия MySQL – 5.0.32.
1 Начнем с данных
Для тестирования создадим небольшую табличку и наполним ее чем-нибудь.
CREATE TABLE items (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
height INT UNSIGNED NOT NULL DEFAULT 0,
width INT UNSIGNED NOT NULL DEFAULT 0,
price DECIMAL(10,2) NOT NULL DEFAULT 0.0,
title VARCHAR(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=UTF8;
Заносим это все в нашу БД. И можно начинать…
2 Обычный метод постраничного вывода
Все, кто уже знает, чем плохи COUNT(*) и LIMIT… OFFSET, могут пропустить эту часть.
Прежде, чем рисовать навигатор, мы делаем SELECT COUNT(*) … WHERE (условия_выборки). Многие, почему-то уверены, что даже если у нас миллионы записей, но условия_выборки позволяют использовать индекс, то такой запрос отработает очень быстро. Проведем эксперимент. Выберем количество записей, у которых height больше 100. Для начала посмотрим, что будет, если индекса по полю height нет.
SELECT SQL_NO_CACHE COUNT(*) FROM items WHERE height>100;
+———-+
| count(*) |
+———-+
| 16405 |
+———-+
1 row in set (0.09 sec)
SHOW STATUS LIKE ‘handler%’;
Последняя команда покажет, сколько же, и каких действий пришлось сделать СУБД, чтобы выполнить наш запрос. Так как индекса у нас нет, MySQL пришлось читать данные прямо из таблицы, поэтому нас интересует строка:
…
| Handler_read_rnd_next | 100001 |
…
То есть, MySQL пришлось сделать 100001 операцию перехода к следующей записи, чтобы найти все, соответствующие запросу.
Везде ниже перед каждым SELECT подразумевается выполнение FLUSH STATUS, а после: SHOW STATUS LIKE ‘handler%’.
Чем нам может помочь индекс.
ALTER TABLE items ADD INDEX height_idx (height);
SELECT SQL_NO_CACHE COUNT(*) FROM items FORCE INDEX(height_idx) WHERE height>100;
+———-+
| count(*) |
+———-+
| 16405 |
+———-+
1 row in set (0.04 sec)
В данном случае использовался индекс, поэтому Handler_read_rnd_next будет равен 0, а вот
…
| Handler_read_next | 16405 |
…
То есть индекс позволяет изначально посчитать только те записи, которые нужны, НО ему все равно требуется пробежаться по ним всем. Нет никакой магии, MySQL нигде не хранит количество записей, соответствующих запросу. Поэтому, если у вас миллионы записей, соответствующих условиям запроса, COUNT будет работать очень медленно.
Второй момент. LIMIT … OFFSET. Тот же эксперимент. Попросим нам 5 записей.
SELECT SQL_NO_CACHE * FROM items FORCE INDEX(height_idx) WHERE height>100 LIMIT 5;
…
5 rows in set (0.00 sec)
…
| Handler_read_next | 4 |
…
Вроде все логично. А теперь попросим вернуть другие 5 записей, начиная с 16401.
SELECT SQL_NO_CACHE * FROM items FORCE INDEX(height_idx) WHERE height>100 LIMIT 16400, 5;
…
5 rows in set (0.13 sec)
Видим, что время выборки значительно возросло. Смотрим статус:
…
| Handler_read_next | 16404 |
…
То есть, MySQL, прочитал все 16405, а только потом просто откинул все ненужные.
Как быть?
3 Как быть
Итак. От нас требуется вывести 10 записей, а так же нарисовать меню навигации. Мы поняли, что, чтобы дойти до записи, с которой необходимо начать отдавать нам результаты, MySQL тратит много лишних действий. Единственное, как это избежать – сразу перейти к нужной, изменив условия выборки.
Рассмотрим все на простом примере: пусть записи выдаются отсортированные по id. В этом случае нам нужно вместе со ссылкой передать id записи, на которой мы остановились. А остановимся мы на записи с То есть, в параметрах ссылки на следующую страницу нам нужно передать 10. Соответственно, для второй страницы запрос будет выглядеть так:
SELECT SQL_NO_CACHE id FROM items WHERE id>10 ORDER BY id LIMIT 10;
Кстати, в обоих случаях Handler_read_next будет равен 9. То есть, прыгнули на первую соответствующую запросу запись (благодаря индексу) и сделали 9 переходов на следующую. Самое главное, что какое бы число вместо 10 в условие мы не подставили – мы всегда в результате SHOW STATUS увидим одно и то же, и время выполнения такого запроса уже не будет зависеть от того, где мы находимся, а будет зависеть только от того, сколько и чего мы выбираем.
Надеюсь, смысл Вам понятен. Давайте тогда решим, что делать с меню навигации, а потом перейдем к более сложной ситуации. Пусть в url мы используем ключевые слова next, previous и last. В каких случаях показывать ссылки «вперед», «назад» и «последняя»?
Каждый раз, когда нам приходит next (запрос следующей страницы), мы выбираем не 10 записей, а 11, начиная с id, переданного в параметрах запроса. Если нам вернулось 11 записей, то следует показать ссылку вперед с id 10-й записи, а 11-ю откинуть. Если вернулось меньше 11 записей, то ссылку вперед показывать не надо. При этом мы всегда (всегда, когда пришло next) показываем ссылку назад (previous) с id первой записи из выборки. Ссылки «в начало» и «последняя» всегда показываются вместе с «назад» и «вперед» соответственно. То есть, если мы решили показывать «назад», то должны показать и «в начало».
Каждый раз, когда нам приходит previous (запрос на предыдущую страницу), мы выбираем 11 записей, у которых id меньше указанного в запросе, отсортированные в обратном порядке. То же самое: если вернулось 11 записей, то ссылку «назад» показываем. Ссылку вперед показываем всегда.
Надеюсь, понятно написал…
Что если нам пришел запрос «last»? Просто показать 10 записей, начиная с самой последней. То есть:
SELECT id FROM items ORDER BY id DESC LIMIT 10;
Думаете, у кого-нибудь из пользователей хватит сил промотать несколько сотен, а то и тысяч страниц, для того, чтобы обвинить Вас во лжи, обнаружив в итоге на первой странице не 10 записей? Даже если и хватит, то Вы можете ответить, что он слишком долго мотал…
Предыдущий пример был прост тем, что id – уникален. А что если требуется сортировка по полю, значения которого могут повторяться? Например, height в нашем случае. Простым запросом было выяснено, что в таблице каждое значения height встречается примерно 800 раз. Просто передать последний выведенный height в параметрах запроса уже мало. Поможет нам всё тот же id. От нас просят отсортировать записи по высоте, но это ведь не мешает нам отсортировать их потом еще и по id?
Для этого нам понадобится новый индекс:
ALTER TABLE items ADD KEY height_id_idx (height, id);
Запрос для первой страницы будет такой:
SELECT SQL_NO_CACHE id, height FROM items ORDER BY height, id LIMIT 10;
В моих результатах у последней записи height=0, Так и надо передать следующей странице. Например, next_0_1174 или next/0/1074 – как Вам удобнее.
Теперь нам нужно выбрать записи, у которых либо height больше 0, либо height=0, а id>1174 (именно для этого мы и сделали дополнительную сортировку).
То есть:
SELECT SQL_NO_CACHE * FROM items WHERE (height>0) OR (height=0 AND id>1174) ORDER BY height, id LIMIT 10;
Надеюсь, пояснять, почему так, не нужно. Статус по-прежнему показывает всего 9 шагов вперед.
Таким образом, мы можем добавлять и другие сортировки. Например, если мы хотим вывести все записи, отсортированные по цене и высоте, запрос будет таким:
SELECT SQL_NO_CACHE * FROM items WHERE (price>5) OR (price=5 AND height>0) AND (price=5 AND height=0 AND id>1174) ORDER BY price, height, id LIMIT 10;
Остается только передавать все необходимые данные, правильно их обрабатывать и подставлять в запрос. И про индекс не забудьте.
4 Что делать с количеством результатов
Как быть, если мы хотим показать пользователям, сколько же результатов найдено? Так как речь идет о больших числах, то вряд ли нас кто-то будет проверять. Тот же гугл может выдать, что нашел 1000000 страниц, соответствующих запросу, но больше 1000 вы не увидите. Мы тоже можем выдать количество результатов лишь примерно. Где его взять и как оценить? Помните, мы выполняли запрос:
SELECT SQL_NO_CACHE COUNT(*) FROM items FORCE INDEX(height_idx) WHERE height>100;
А давайте сделаем так:
EXPLAIN SELECT SQL_NO_CACHE * FROM items FORCE INDEX(height_idx) WHERE height>100;
Столбец rows как раз показывает оценочное число записей, которые нужно просмотреть. 22616 и 16405 – разница совсем не велика. Можно округлить до
20000, да и ладно. Сойдёт. Только помните, что если используете, например, подзапросы и/или объединения, то EXPLAIN вернет несколько строк. Их все надо прочитать и перемножить значения rows.
Постраничная навигация PHP и MySQL
Уважаемые пользователи! Мы благодарим Вас за то, что Вам интересен нашен контент, поэтому с каждым днем хотим становиться все лучше и лучше!
Большое спасибо за вашу помощь и внимательность к нам!
Постраничная навигация есть практически на каждом сайте, так как является неотъемлемой его частью, позволяет удобно и правильно использовать ресурс в плане перехода с одной страницы на другую, экономя при этом значительно скорость загрузки ресурса в целом. В данном материале, мы рассмотрим постраничную навигацию выполненную посредством языков веб программированию php и mysql.
Таблица materials.
Таблица navigation.
Для начала, давайте подключимся к базе данных MySQL.
Подключаем постраничную навигацию, подробно все расписано в комментариях. Из основных моментов, хотелось бы отметить, что количество материалов на странице, я решил занести в базу данных исходя из следующих соображений.
Во-первых, таким образом будет проще изменять количество материалов с помощью обновления при обращении к таблице navigation. Во-вторых, не потребуется водить значение количества материалов в переменную. При перемещении по навигации, задействована данная страница index.php, поэтому если у Вас навигация будет находится в другом файле, который по другому назван, соответственно исправьте на нужный!
Предадим немного стилей нашей навигации.
Надеюсь, данный урок был Вам полезен и возможно где-то пригодиться или уже пригодился! Если же вдруг Вам не нужна постраничная навигация, а примеру Вы хотите чтобы данные подгружались, советую рассмотреть материал AJAX подгрузка контента при прокрутке, возможно она Вам больше пригодится!
Универсальный постраничный вывод на PHP
Наверное, каждый разработчик сталкивался с необходимостью постраничного вывода какой-либо информации на экран. Практически в каждом проекте всегда есть «что-то», что не помещается на одну страницу и его нужно вывести по частям, классическим примером этому может служить, вывод информации в yandex или google. Все видели панель навигации, в низу страницы, между страницами, как сделать подобную панель навигации, и посвящена эта статья.
Так будет выглядеть панель навигации, которая описана в этой статье. Итак, приступим. Все комментарии я буду выдавать по ходу работы.
Но сейчас немного теории и принципа работы данного скрипта
Приблизительно так выглядит панель навигации в yandex, когда вы находитесь на 8 странице.
Как видно в примере 3, количество ссылок изменилось. Немного лучше выглядит панель навигации, в которой количество ссылок постоянно, но в таком скрипте больше кода.
Ниже приведен скрипт простейшего постраничного вывода, а также скрипты обеих навигационных панелей, т.е. с постоянным количеством ссылок и с отсеченными по «краям».
Скрипт 1. Простейший постраничный вывод
Скрипт 2. С постоянным количеством ссылок
Скрипт 3. С отсеченными по «краям» ссылками
Вот и все! Универсальный постраничный вывод готов ;).
Дополнение применительно к MySQL
раньше это выглядело так:
всё же на самом деле в несколько раз проще и понятнее если немного поглубже ознакомится с MySQL того же самого можно добиться след. запросом:
Пагинатор. Постраничный вывод данных на php
В этой статье я приведу пример работы пагинатора — вывода записей из базы данных постранично. Для примера будем выводить новости.
Весь функционал опирается на возможность в sql делать выборку по лимиту, для этого в запрос добавляется ключевое слово — LIMIT. Limit может принимать два параметра rows и offset. Выглядеть это будет примерно так:
В этом запросе мы достаем из таблицы table 10 записей, начиная с пятой.
Теперь применим возможности limit для написания пагинатора.
Для начала нужно создать таблицу, с тестовыми данными, можете применить этот патч:
Теперь напишем php код, который будет отвечать за логику выборки данных и построение пагинатора:
Код скрипта db_connection.php:
Теперь сделаем вывод данных и пагинатора в браузере:
Вот и все, пагинатор готов. Скачать исходники можно тут
Пагинатор. Постраничный вывод данных на php: 9 комментариев
вот бы тоже самое только с базой на текстовых файлах
разбиваешь через explode и вперед
Спасибо, все очень просто и понятно.
например у меня есть статья, которая представляет из себя html файл
и мне нужно организовать постраничный вывод статьи..
p.s. нужен пример пагинатора без СУБД
Спасибо Вам большое! Ваша статья — лучшее, что я смог найти по пагинаторам.
Отличный и простой пагинатор, я еще новичок в php но в свой двиг впаял его как надо. Огромное спасибо за то что выложили хороший скрипт. Подскажите вот какой момент, а если на сайте 100.000 файлов, выдаст слишком много страниц, как это дело все компактно сделать?
Нужно условие делать для вывода количества номеров страниц — первые 2-3 штуки, последние 2-3, активная ссылка, активная минус один и активная плюс один.
тогда красиво получится, что-то типо такого: 1 2 3 … 6 7 8 … 500 501 502
Можете пожалуйста скинуть в добавок и код с условием думаю очень многим новичкам пригодиться. Я вот искал долго пагинатор, на всех сайтах сложные замороченные а у вас самый дельный плюс еще хорошо что сразу с выводом файлов, но думаю компактность будет нелишней. Дополните пожалуйста статью если нетрудно, а то я сам поламаю свой код:-) Ищу вот еще поиск хороший на php что бы искать к примеру по titles в базе, есть у вас статьи по теме?