Esegui una query con LIMIT / OFFSET e ottieni anche il numero totale di righe


103

Ai fini dell'impaginazione, ho bisogno di eseguire una query con le clausole LIMITe OFFSET. Ma ho anche bisogno di un conteggio del numero di righe che verrebbero restituite da quella query senza le clausole LIMITe OFFSET.

Voglio correre:

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

E:

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

Allo stesso tempo. C'è un modo per farlo, in particolare un modo che consenta a Postgres di ottimizzarlo, in modo che sia più veloce che eseguirli entrambi individualmente?


Risposte:


179

Sì. Con una semplice funzione finestra:

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

Tieni presente che il costo sarà notevolmente superiore rispetto a quello senza il numero totale, ma in genere ancora più economico di due query separate. Postgres deve effettivamente contare tutte le righe in entrambi i modi, il che impone un costo in base al numero totale di righe qualificate. Dettagli:

Tuttavia , come ha sottolineato Dani , quando OFFSETè grande almeno quanto il numero di righe restituite dalla query di base, non viene restituita alcuna riga. Quindi anche noi non otteniamo full_count.

Se ciò non è accettabile, una possibile soluzione alternativa per restituire sempre il conteggio completo sarebbe con un CTE e un 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;

Ottieni una riga di valori NULL con l' full_countaggiunta se OFFSETè troppo grande. Altrimenti, viene aggiunto a ogni riga come nella prima query.

Se una riga con tutti i valori NULL è un possibile risultato valido, è necessario verificare offset >= full_countper disambiguare l'origine della riga vuota.

Ciò esegue ancora la query di base solo una volta. Ma aggiunge più overhead alla query e paga solo se è inferiore alla ripetizione della query di base per il conteggio.

Se sono disponibili indici che supportano l'ordinamento finale, potrebbe essere conveniente includerli ORDER BYnella CTE (in modo ridondante).


3
Sia per LIMIT che per condizioni, abbiamo righe da restituire, ma con l'offset dato non restituirebbe alcun risultato. In quella situazione, come potremmo ottenere il conteggio delle righe?
Dani Mathew

molto bello, grazie, funziona alla grande quando usi l'impaginazione, i datatables, aggiungilo all'inizio del tuo sql e usalo, salva una query extra per il conteggio totale.
Ahmed Sunny

Potresti approfondire questo se il conteggio potesse essere abilitato dinamicamente nella query tramite un parametro di input? Ho un requisito simile, ma l'utente decide se desidera o meno il conteggio in linea.
julealgon

1
@julealgon: inizia una nuova domanda con i dettagli che definiscono. Puoi sempre collegarti a questo per contesto e aggiungere lasciare un commento qui per ricollegarlo (e attirare la mia attenzione) se lo desideri.
Erwin Brandstetter

1
@JustinL .: L'overhead aggiunto dovrebbe essere significativo solo per query di base relativamente economiche. Inoltre, Postgres 12 ha migliorato le prestazioni CTE in diversi modi. (Anche se questo CTE è ancora MATERIALIZEDper impostazione predefinita, viene menzionato due volte.)
Erwin Brandstetter

0

modifica: questa risposta è valida quando si recupera la tabella non filtrata. Lo lascerò nel caso possa aiutare qualcuno, ma potrebbe non rispondere esattamente alla domanda iniziale.

La risposta di Erwin Brandstetter è perfetta se hai bisogno di un valore accurato. Tuttavia, su tavoli di grandi dimensioni spesso è necessaria solo un'approssimazione piuttosto buona. Postgres ti dà proprio questo e sarà molto più veloce in quanto non avrà bisogno di valutare ogni riga:

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;

In realtà non sono abbastanza sicuro se ci sia un vantaggio per esternalizzare RIGHT JOINo averlo come in una query standard. Meriterebbe qualche prova.

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
Informazioni sulla stima del conteggio rapido: stackoverflow.com/a/7945274/939860 Come hai detto tu: valido durante il recupero dell'intera tabella, il che è contraddetto dalla WHEREclausola nelle tue query. La seconda query è logicamente sbagliata (recupera una riga per ogni tabella nel database) e più costosa una volta risolta.
Erwin Brandstetter

0

Anche se la risposta di Erwin Brandstetter funziona come un fascino, restituisce il conteggio totale delle righe in ogni riga come segue:

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

Potresti prendere in considerazione l'utilizzo di un approccio che restituisca il conteggio totale solo una volta , come il seguente:

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

Query 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 

-6

È una cattiva pratica chiamare due volte la stessa query solo per ottenere il numero totale di righe del risultato di ritorno. Ci vorrà tempo di esecuzione e sprecherà la risorsa del server.

Meglio, puoi usare SQL_CALC_FOUND_ROWSnella query che dirà a MySQL di recuperare il numero totale di conteggi di righe insieme ai risultati della query limite.

Esempio impostato come:

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

SELECT FOUND_ROWS();

Nella query precedente, aggiungi semplicemente l' SQL_CALC_FOUND_ROWSopzione nel resto della query richiesta ed esegui la seconda riga, ovvero SELECT FOUND_ROWS()restituisce il numero di righe nel set di risultati restituito da tale istruzione.


1
La soluzione richiede postgres, non mysql.
MuffinMan

@ MuffinMan, puoi usare lo stesso su mysql. A partire da MYSQL 4.0, viene utilizzata l'opzione SQL_CALC_FOUND_ROWS nella query. Ma da MYSQL 8.0 è deprecato.
Mohd Rashid

Non rilevante. Questa domanda ha avuto risposta anni fa. Se vuoi contribuire, pubblica una nuova domanda con lo stesso argomento ma specifica per MySQL.
MuffinMan

essere sempre pertinente
Ali Hussain

-15

No.

C'è forse qualche piccolo guadagno che potresti teoricamente ottenere eseguendoli individualmente con macchinari abbastanza complicati sotto il cofano. Tuttavia, se vuoi sapere quante righe corrispondono a una condizione, dovrai contarle anziché solo un sottoinsieme LIMITATO.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.