Лучший способ получить счетчик результатов до применения LIMIT - PullRequest
55 голосов
/ 01 октября 2008

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

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

Это кажется неэффективным. Есть ли лучший способ определить, сколько результатов было бы возвращено до применения LIMIT?

Я использую PHP и Postgres.

Ответы [ 5 ]

117 голосов
/ 23 ноября 2011

Чистый SQL

С 2008 года все изменилось. Вы можете использовать оконную функцию , чтобы получить полный счет и ограниченный результат в одном запросе. (Введено в PostgreSQL 8.4 в 2009 ).

SELECT foo
     , count(*) OVER() AS full_count
FROM   bar
WHERE  <some condition>
ORDER  BY <some col>
LIMIT  <pagesize>
OFFSET <offset>

Обратите внимание, что это может быть значительно дороже, чем без общего количества. Все строки должны быть подсчитаны, и возможный ярлык, извлекающий только верхние строки из соответствующего индекса, может больше не помогать.
Не имеет большого значения для небольших таблиц или full_count <= <code>OFFSET + LIMIT. Вопросы для значительно большего full_count.

Угловой регистр : когда OFFSET не меньше, чем количество строк из базового запроса, без строки возвращается. Таким образом, вы также не получите full_count. Возможная альтернатива:

Рассмотрим последовательность событий :

  1. WHERE предложение (и JOIN условия, но не здесь) фильтруют строки квалификации из базовой таблицы (таблиц).

    (GROUP BY и агрегатные функции будут здесь.)

  2. Оконные функции применяются с учетом всех подходящих строк (в зависимости от предложения OVER и спецификации фрейма функции). Простое count(*) OVER() основано на всех строках.

  3. ORDER BY

    (DISTINCT или DISTINCT ON пошли бы сюда.)

  4. LIMIT / OFFSET применяются на основе установленного порядка выбора строк для возврата.

LIMIT / OFFSET становится все более неэффективным с ростом числа строк в таблице. Рассмотрите альтернативные подходы, если вам нужна лучшая производительность:

Альтернативы, чтобы получить окончательный счет

Существуют совершенно разные подходы, чтобы получить количество затронутых строк ( не полный счет до применения OFFSET & LIMIT). Postgres имеет внутреннюю учетную запись о количестве строк, затронутых последней командой SQL. Некоторые клиенты могут получить доступ к этой информации или сами считать строки (например, psql).

Например, вы можете получить количество затронутых строк в plpgsql сразу после выполнения команды SQL с помощью:

GET DIAGNOSTICS integer_var = ROW_COUNT;

Подробности в руководстве.

Или вы можете использовать pg_num_rows в PHP . Или аналогичные функции в других клиентах.

Связанный:

5 голосов
/ 01 октября 2008

Как я описал в моем блоге , MySQL имеет функцию под названием SQL_CALC_FOUND_ROWS . Это устраняет необходимость выполнять запрос дважды, но ему все равно необходимо выполнить запрос целиком, даже если предложение limit позволило бы остановить его раньше.

Насколько я знаю, подобной функции для PostgreSQL не существует. Одна вещь, на которую следует обращать внимание при разбивке на страницы (наиболее распространенная вещь, для которой LIMIT используется IMHO): выполнение «OFFSET 1000 LIMIT 10» означает, что БД должна извлечь как минимум 1010 строк, даже если он дает вам только 10. Более эффективный способ сделать это - запомнить значение строки, по которой вы упорядочиваете предыдущую строку (в данном случае 1000-ую), и переписать запрос следующим образом: "... WHERE order_row> value_of_1000_th LIMIT 10 ". Преимущество заключается в том, что "order_row", скорее всего, проиндексирован (если нет, у вас возникла проблема). Недостатком является то, что если новые элементы добавляются между просмотрами страниц, это может немного нарушиться (но, опять же, это может не наблюдаться посетителями и может привести к значительному увеличению производительности).

1 голос
/ 01 октября 2008

Вы можете уменьшить потери производительности, не выполняя запрос COUNT () каждый раз. Кэшируйте количество страниц, скажем, за 5 минут до повторного запуска запроса. Если вы не видите огромное количество INSERT, это должно работать просто отлично.

0 голосов
/ 26 сентября 2009

Поскольку Postgres уже выполняет определенное количество операций кэширования, этот тип методов не так неэффективен, как кажется. Это определенно не удваивает время выполнения. У нас есть таймеры, встроенные в наш уровень БД, поэтому я видел доказательства.

0 голосов
/ 01 октября 2008

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

Если вы выполняете запрос COUNT с целью решить, предоставлять ли данные пользователю или нет (т. Е. Если> X записей, возвращать ошибку), вам необходимо придерживаться подхода COUNT.

...