Запустіть запит з обмеженням / зміщенням, а також отримайте загальну кількість рядків


90

Для цілей пагінації мені потрібен запуск запиту з пропозиціями LIMITand OFFSET. Але мені також потрібен підрахунок кількості рядків, які будуть повернуті цим запитом без пропозицій LIMITand та OFFSET.

Я хочу запустити:

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

І:

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

В той самий час. Чи є спосіб зробити це, зокрема спосіб, який дозволяє Postgres оптимізувати його, так що це швидше, ніж запуск обох окремо?


Відповіді:


168

Так. За допомогою простої функції вікна:

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 (зайве).


3
Як за ОБМЕЖЕННЯМ, так і за умовами, ми маємо повертати рядки, але з заданим зміщенням це не поверне результату. У такій ситуації, як би ми змогли отримати підрахунок рядків?
Дані Метью

дуже добре, дякую, чудово працює, коли ви використовуєте пагінацію, таблиці даних, просто додайте це на початку вашого sql і використовуйте його, збережіть додатковий запит для загальної кількості.
Ахмед Сонячний

Чи не могли б Ви детальніше розповісти про це, якби підрахунок можна було динамічно активувати у запиті через вхідний параметр? Я маю подібну вимогу, але користувач вирішує, хоче він вбудований підрахунок чи ні.
julealgon

1
@julealgon: Будь ласка, розпочніть нове запитання із зазначенням деталей. Ви завжди можете зробити посилання на це для контексту та додати залишити коментар тут, щоб повернути посилання (і привернути мою увагу), якщо хочете.
Ервін Брандштеттер

1
@JustinL .: Додані накладні витрати повинні бути значними лише для відносно дешевих базових запитів. Крім того, Postgres 12 покращив продуктивність CTE різними способами. (Хоча цей КТЕ все ще є MATERIALIZEDза замовчуванням, на нього посилаються двічі.)
Ервін Брандштеттер

1

редагувати: ця відповідь дійсна при отриманні нефільтрованої таблиці. Я дам це на випадок, якщо це може комусь допомогти, але це може не точно відповісти на початкове питання.

Відповідь Ервіна Брандштеттера ідеально підходить, якщо вам потрібне точне значення. Однак на великих столах вам часто потрібне лише досить гарне наближення. 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 ?

2
Про швидку оцінку підрахунку: stackoverflow.com/a/7945274/939860 Як ви вже сказали: дійсний при отриманні цілої таблиці - що суперечить WHEREпункту у ваших запитах. Другий запит це логічно неправильно (отримує один рядок для кожної таблиці в БД) - і дорожче, якщо це виправлено.
Ервін Брандштеттер,

-7

Погана практика викликати два рази однаковий запит для Just, щоб отримати загальну кількість рядків результату повернення. Це займе час виконання та витратить ресурс сервера.

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


1
Рішення вимагає postgres, а не mysql.
MuffinMan

@MuffinMan, ти можеш використовувати те саме на mysql. Починаючи з MYSQL 4.0, у запиті використовується опція SQL_CALC_FOUND_ROWS. Але з MYSQL 8.0 це застаріло.
Мохд Рашид

Не стосується. На це питання відповіли роки тому. Якщо ви хочете зробити свій внесок, опублікуйте нове запитання з тією ж темою, але специфічним для MySQL.
MuffinMan

завжди бути актуальним
Алі Хуссейн,

-14

Немає.

Можливо, є якийсь невеликий виграш, який ви теоретично могли б отримати, якщо запускати їх окремо, маючи досить складну техніку під капотом. Але, якщо ви хочете знати, скільки рядків відповідає умові, вам доведеться їх рахувати, а не просто ОБМЕЖЕНУ підмножину.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.