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

Я хочу бежать:

SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?

А также:

SELECT COUNT(*) FROM table WHERE /* whatever */

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

146
Tim 6 Мар 2015 в 00:45

5 ответов

Лучший ответ

Да. С помощью простой функции окна:

SELECT *, count(*) OVER() AS full_count
FROM   tbl
WHERE  /* whatever */
ORDER  BY col1
OFFSET ?
LIMIT  ?

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

Однако , как указала Дэни, когда OFFSET не меньше, чем количество строк, возвращаемых из базового запроса, строки не возвращаются . Так что мы также не получаем full_count.

Если это неприемлемо, возможное обходное решение, чтобы всегда возвращать полный счет , было бы с CTE и OUTER JOIN:

WITH cte AS (
   SELECT *
   FROM   tbl
   WHERE  /* whatever */
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY col1
   LIMIT  ?
   OFFSET ?
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(full_count) ON true;

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

Если строка со всеми значениями NULL является возможным допустимым результатом, вы должны проверить offset >= full_count, чтобы устранить неоднозначность происхождения пустой строки.

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

Если доступны индексы, поддерживающие окончательный порядок сортировки, может быть полезно включить ORDER BY в CTE (избыточно).

180
Erwin Brandstetter 23 Мар 2020 в 23:32

Нет.

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

-15
Richard Huxton 5 Мар 2015 в 21:56

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

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

Пример установлен как:

SELECT SQL_CALC_FOUND_ROWS employeeName, phoneNumber FROM employee WHERE employeeName LIKE 'a%' LIMIT 10;

SELECT FOUND_ROWS();

В приведенном выше запросе просто добавьте параметр SQL_CALC_FOUND_ROWS в оставшийся требуемый запрос и выполните вторую строку, т.е. SELECT FOUND_ROWS() возвращает количество строк в наборе результатов, возвращаемом этим оператором.

-6
Shree 3 Окт 2019 в 09:09

Хотя ответ Эрвина Брандштеттера работает как шарм, он возвращает общее количество строк в каждой строке как следующее:

col1 - col2 - col3 - total
--------------------------
aaaa - aaaa - aaaa - count
bbbb - bbbb - bbbb - count
cccc - cccc - cccc - count

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

total - rows
------------
count - [{col1: 'aaaa'},{col2: 'aaaa'},{col3: 'aaaa'}
         {col1: 'bbbb'},{col2: 'bbbb'},{col3: 'bbbb'}
         {col1: 'cccc'},{col2: 'cccc'},{col3: 'cccc'}]

SQL-запрос:

SELECT 
    (SELECT COUNT(*) FROM table) as count, 
    (SELECT json_agg(t.*) FROM (
        SELECT * FROM table
        WHERE /* whatever */
        ORDER BY col1
        OFFSET ?
        LIMIT ?
    ) AS t) AS rows 
0
treecon 28 Дек 2020 в 14:48

Edit: этот ответ действителен при получении неотфильтрованной таблицы. Я позволю, если это может кому-то помочь, но может не совсем ответить на первоначальный вопрос.

Ответ Эрвина Брандштеттера идеален, если вам нужно точное значение. Однако для больших таблиц часто требуется только довольно хорошее приближение. Postgres дает вам именно это, и это будет намного быстрее, так как не нужно будет оценивать каждую строку :

SELECT *
FROM (
    SELECT *
    FROM tbl
    WHERE /* something */
    ORDER BY /* something */
    OFFSET ?
    LIMIT ?
    ) data
RIGHT JOIN (SELECT reltuples FROM pg_class WHERE relname = 'tbl') pg_count(total_count) ON true;

На самом деле я совершенно не уверен, есть ли преимущество во внешнем использовании RIGHT JOIN или использовании его как в стандартном запросе. Это заслуживает некоторого тестирования.

SELECT t.*, pgc.reltuples AS total_count
FROM tbl as t
RIGHT JOIN pg_class pgc ON pgc.relname = 'tbl'
WHERE /* something */
ORDER BY /* something */
OFFSET ?
LIMIT ?
0
François Gueguen 16 Апр 2020 в 05:04