2009-05-04 8 views
92

Mi chiedevo se c'era un modo per ottenere il numero di risultati da una query MySQL e allo stesso tempo limitare i risultati.Impaginazione MySQL senza doppia interrogazione?

I lavori di impaginazione modo (se ho capito bene), per prima cosa fare qualcosa di simile

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

Dopo ho la numero_colonne (query), ho il numero di risultati. Ma poi di limitare in realtà i miei risultati, devo fare una seconda query del tipo:

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

La mia domanda: Esiste un modo per entrambi recuperare il numero totale di risultati che sarebbe dato, e limitare i risultati restituiti in un domanda singola? O un modo più efficace per farlo. Grazie!

+2

Sebbene non si abbia COUNT (*) in query2 – dlofrodloh

risposta

54

No, questo è il numero di applicazioni che vogliono impaginare devono farlo. È affidabile e a prova di proiettile, anche se rende la query due volte. Ma puoi memorizzare il conto per qualche secondo e questo ti aiuterà molto.

L'altro modo è utilizzare la clausola SQL_CALC_FOUND_ROWS e quindi chiamare SELECT FOUND_ROWS(). a parte il fatto che devi chiamare in seguito la chiamata FOUND_ROWS(), c'è un problema con questo: c'è a bug in MySQL che questo tickles che interessa le query ORDER BY rende molto più lento su tabelle di grandi dimensioni rispetto all'approccio ingenuo di due query.

+1

Eccellente, grazie per il vostro aiuto! – ash

+2

Tuttavia, non è affatto una prova di razza, a meno che non si facciano le due query all'interno di una transazione. Questo generalmente non è un problema, però. – NickZoic

+0

Con "affidabile" intendevo che l'SQL stesso restituiva sempre il risultato desiderato e, per "a prova di proiettile", intendevo dire che non ci sono bug di MySQL che ostacolano l'SQL che è possibile utilizzare. A differenza di SQL_CALC_FOUND_ROWS con ORDER BY e LIMIT, secondo l'errore che ho citato. – staticsan

2
query = SELECT col, col2, (SELECT COUNT(*) FROM `table`) AS total FROM `table` WHERE `some_condition` LIMIT 0, 10 
+13

Questa query restituisce semplicemente il numero totale di record nella tabella; non il numero di record che corrispondono alla condizione. –

+1

Il numero totale di record è ciò che è necessario per l'impaginazione (@Lawrence). – imme

14

Nella maggior parte delle situazioni è molto più veloce e meno dispendioso in termini di risorse farlo in due query separate piuttosto che farlo in una sola, anche se ciò sembra contro-intuitivo.

Se si utilizza SQL_CALC_FOUND_ROWS, quindi per tabelle di grandi dimensioni rende la query molto più lenta, notevolmente più lenta persino dell'esecuzione di due query, la prima con COUNT (*) e la seconda con LIMIT. La ragione di ciò è che SQL_CALC_FOUND_ROWS fa sì che la clausola LIMIT venga applicata dopo il che preleva le righe anziché prima, quindi recupera l'intera riga per tutti i possibili risultati prima di applicare i limiti. Questo non può essere soddisfatto da un indice perché in realtà recupera i dati.

Se si utilizza l'approccio a due query, il primo recupera solo COUNT (*) e non i dati effettivamente recuperati e effettivi, questo può essere soddisfatto molto più rapidamente perché solitamente può utilizzare gli indici e non deve recuperare il dati di riga effettivi per ogni riga che guarda. Quindi, la seconda query deve solo esaminare le prime $ limit $ limit e quindi restituire.

Questo post dal blog prestazioni MySQL spiega questo ulteriore:

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

Per ulteriori informazioni su come ottimizzare l'impaginazione, controllare this post e this post.

64

Non faccio quasi mai due domande.

È sufficiente restituire una riga in più del necessario, visualizzare solo 10 sulla pagina e, se sono presenti più di, vengono visualizzati un pulsante "Avanti".

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. 

Vostri criteri deve restituire in un ordine di più rilevanti prima.Le probabilità sono, la maggior parte delle persone non si preoccuperà di andare a pagina 236 su 412.

Quando si fa una ricerca su google, ei risultati non sono sulla prima pagina, probabilmente vai alla seconda pagina, non nove.

+0

Questo è vero, lo terrò a mente. – ash

+28

In realtà, se non lo trovo nella prima pagina di una query di Google, di solito salgo alla pagina nove. – Phil

+3

@Phil Ho sentito prima, ma perché farlo? – TK123

22

Un altro approccio per evitare la doppia interrogazione consiste nel recuperare tutte le righe per la pagina corrente utilizzando prima una clausola LIMIT, quindi eseguire una seconda query COUNT (*) solo se è stato recuperato il numero massimo di righe.

In molte applicazioni, il risultato più probabile sarà che tutti i risultati rientrino in una pagina e che la paginazione sia l'eccezione piuttosto che la norma. In questi casi, la prima query non recupererà il numero massimo di risultati.

Ad esempio, le risposte su una domanda StackOverflow si riversano raramente su una seconda pagina. I commenti su una risposta raramente superano il limite di 5 circa richiesto per mostrarli tutti.

Quindi in queste applicazioni è sufficiente eseguire prima una query con LIMIT e quindi finché non viene raggiunto tale limite, si conosce esattamente quante righe ci sono senza la necessità di eseguire un secondo COUNT (*) query - che dovrebbe coprire la maggior parte delle situazioni.

+1

Punto eccellente! –

+1

@thomasrutter Ho avuto lo stesso approccio, tuttavia ho scoperto un difetto con esso oggi. La pagina finale dei risultati non avrà quindi i dati di impaginazione. cioè, diciamo che ogni pagina dovrebbe avere 25 risultati, l'ultima pagina probabilmente non ne ha così tanti, diciamo che ha 7 ... il che significa che il conteggio (*) non verrà mai eseguito, quindi nessuna impaginazione verrà mostrata al utente. – duellsy

+1

No, se dici 200 risultati, esegui una query nei 25 successivi e ottieni solo 7 indietro, che ti dice che il numero totale di risultati è 207 e quindi non hai bisogno di fare un'altra query con COUNT (*) perché sai già cosa sta per dire. Hai tutte le informazioni che ti servono per mostrare l'impaginazione. Se si verifica un problema con la paginazione che non viene mostrata all'utente, si ha un errore da qualche altra parte. – thomasrutter

2

La mia risposta potrebbe essere in ritardo, ma puoi saltare la seconda query (con il limite) e filtrare semplicemente le informazioni tramite lo script di back-end. In PHP, per esempio, si potrebbe fare qualcosa di simile:

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

Ma naturalmente, quando si dispone di migliaia di record da considerare, diventa inefficiente molto veloce. Il conteggio precalcolato potrebbe essere una buona idea da esaminare.

Ecco una buona lettura sul tema: http://www.percona.com/ppc2009/PPC2009_mysql_pagination.pdf

+1

grazie per aver condiviso questo link! – Vikram

+0

Sì, questo collegamento ha esattamente informazioni preziose. –

+0

Link è morto, immagino che sia corretto: http://www.percona.com/files/presentations/ppc2009/PPC2009_mysql_pagination.pdf. Non verrà modificato perché non è sicuro se lo è. – hectorg87

-12
SELECT * 
FROM table 
WHERE some_condition 
ORDER BY RAND() 
LIMIT 0, 10 
+2

Questo non risponde alla domanda, e un ordine di Rand è una pessima idea. –

0

è possibile riutilizzare la maggior parte delle query in una sottoquery e impostarlo su un identificatore. Ad esempio, una query su un film che trova film contenenti l'ordine della lettera "in fase di esecuzione" sarebbe simile al mio sito.

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; 

notano che io non sono un esperto di database, e spero che qualcuno sarà in grado di ottimizzare che un po 'meglio. Mentre sta funzionando direttamente dall'interfaccia della riga di comando SQL, entrambi richiedono circa 0.02 secondi sul mio laptop.