MySQL нумерация страниц без двойных запросов? - PullRequest
101 голосов
/ 04 мая 2009

Мне было интересно, есть ли способ получить количество результатов запроса MySQL и в то же время ограничить результаты.

Как работает разбиение на страницы (насколько я понимаю), сначала я делаю что-то вроде

query = SELECT COUNT(*) FROM `table` WHERE `some_condition`

После того, как я получу num_rows (запрос), у меня будет количество результатов. Но затем, чтобы на самом деле ограничить свои результаты, я должен сделать второй запрос, например:

query2 = SELECT COUNT(*) FROM `table` WHERE `some_condition` LIMIT 0, 10

Мой вопрос: есть ли способ получить общее количество результатов, которые будут предоставлены, И ограничить результаты, возвращаемые в одном запросе? Или любой более эффективный способ сделать это. Спасибо!

Ответы [ 8 ]

64 голосов
/ 24 июля 2010

Я почти никогда не делаю два запроса.

Просто верните на одну строку больше, чем необходимо, отобразите на странице только 10, а если их больше, нажмите кнопку «Далее».

SELECT x, y, z FROM `table` WHERE `some_condition` LIMIT 0, 11
// iterate through and display 10 rows.

// if there were 11 rows, display a "Next" button.

Ваш запрос должен сначала вернуться в порядке наиболее релевантного. Скорее всего, большинство людей не будет заботиться о переходе на страницу 236 из 412.

Когда вы выполняете поиск в Google и ваши результаты не на первой странице, вы, вероятно, переходите на вторую, а не на девятую страницу.

60 голосов
/ 04 мая 2009

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

Другой способ - использовать предложение SQL_CALC_FOUND_ROWS, а затем вызвать SELECT FOUND_ROWS(). Помимо факта, что вы должны сделать вызов FOUND_ROWS() впоследствии, есть проблема с этим: в MySQL есть ошибка , из-за которой эта ошибка срабатывает на ORDER BY запросов, делая ее намного медленнее в больших таблицах чем наивный подход двух запросов.

24 голосов
/ 08 сентября 2011

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

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

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

Таким образом, в этих приложениях вы можете просто сначала выполнить запрос с LIMIT, а затем, если этот предел не достигнут, вы точно знаете, сколько строк без необходимости делать второй запрос COUNT (*) - которые должны охватывать большинство ситуаций.

14 голосов
/ 04 мая 2009

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

Если вы используете SQL_CALC_FOUND_ROWS, то для больших таблиц это делает ваш запрос намного медленнее, значительно медленнее, чем выполнение двух запросов, первый с COUNT (*), а второй с LIMIT. Причина этого заключается в том, что SQL_CALC_FOUND_ROWS вызывает применение предложения LIMIT после выборки строк, а не до, поэтому он выбирает всю строку для всех возможных результатов перед применением ограничений. Это не может быть удовлетворено индексом, потому что он фактически выбирает данные.

Если вы используете подход с двумя запросами, первый только извлекает COUNT (*), а не фактически выбирает и фактические данные, это может быть выполнено намного быстрее, потому что обычно он использует индексы и не должен извлекать фактические данные данные строки для каждой строки, на которую это смотрит. Затем второму запросу нужно только просмотреть первые строки $ offset + $ limit и затем вернуться.

Этот пост из блога производительности MySQL объясняет это далее:

http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Для получения дополнительной информации по оптимизации нумерации страниц, проверьте этот пост и этот пост .

2 голосов
/ 09 апреля 2012

Мой ответ может быть запоздалым, но вы можете пропустить второй запрос (с ограничением) и просто отфильтровать информацию через ваш серверный скрипт. В PHP, например, вы можете сделать что-то вроде:

if($queryResult > 0) {
   $counter = 0;
   foreach($queryResult AS $result) {
       if($counter >= $startAt AND $counter < $numOfRows) {
            //do what you want here
       }
   $counter++;
   }
}

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

Вот хорошее прочтение на эту тему: http://www.percona.com/ppc2009/PPC2009_mysql_pagination.pdf

2 голосов
/ 04 мая 2009
query = SELECT col, col2, (SELECT COUNT(*) FROM `table`) AS total FROM `table` WHERE `some_condition` LIMIT 0, 10
0 голосов
/ 16 июля 2016

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

SELECT Movie.*, (
    SELECT Count(1) FROM Movie
        INNER JOIN MovieGenre 
        ON MovieGenre.MovieId = Movie.Id AND MovieGenre.GenreId = 11
    WHERE Title LIKE '%s%'
) AS Count FROM Movie 
    INNER JOIN MovieGenre 
    ON MovieGenre.MovieId = Movie.Id AND MovieGenre.GenreId = 11
WHERE Title LIKE '%s%' LIMIT 8;

Обратите внимание, что я не эксперт по базам данных, и надеюсь, что кто-то сможет оптимизировать это немного лучше. Поскольку он работает прямо из интерфейса командной строки SQL, они оба занимают ~ 0,02 секунды на моем ноутбуке.

0 голосов
/ 29 октября 2012
SELECT * 
FROM table 
WHERE some_condition 
ORDER BY RAND()
LIMIT 0, 10
...