Il problema con i suggerimenti in questo thread e altrove sul Web è che tutte le soluzioni proposte vengono eseguite in tempo lineare rispetto al numero di record. Ad esempio, considera una query come la seguente.
select *
from
(
select
Row_Number() over (order by ClusteredIndexField) as RowNumber,
*
from MyTable
) as PagedTable
where RowNumber between @LowestRowNumber and @HighestRowNumber;
Quando si ottiene la pagina 1, la query richiede 0,577 secondi. Tuttavia, quando si ottiene la pagina 15.619, questa stessa query richiede più di 2 minuti e 55 secondi.
Possiamo migliorare notevolmente creando un numero di record, tabella incrociata dell'indice come mostrato nella seguente query. Il cross-table è chiamato PagedTable e non è persistente.
select *
from
(
select
Row_Number() over (order by Field1 asc, Field2 asc, Field3 asc) as RowNumber,
ClusteredIndexField
from MyTable
) as PagedTable
left join MyTable on MyTable.ClusteredIndexField = PagedTable.ClusteredIndexField
where RowNumber between @LowestRowNumber and @HighestRowNumber;
Come nell'esempio precedente, l'ho provato su una tabella molto ampia con 780.928 record. Ho usato una dimensione di pagina di 50, che ha provocato 15.619 pagine.
Il tempo totale impiegato per la pagina 1 (la prima pagina) è di 0,413 secondi. Il tempo totale impiegato per la pagina 15.619 (l'ultima pagina) è 0.987 secondi, solo il doppio della durata della pagina 1. Questi tempi sono stati misurati utilizzando SQL Server Profiler e il DBMS era SQL Server 2008 R2.
Questa soluzione funziona in ogni caso quando si ordina la tabella con un indice. L'indice non deve essere in cluster o semplice. Nel mio caso, l'indice era composto da tre campi: varchar (50) asc, varchar (15) asc, numerico (19,0) asc. Che la performance sia stata eccellente nonostante l'indice ingombrante dimostra ulteriormente che questo approccio funziona.
Tuttavia, è fondamentale che la clausola order by nella funzione di windowing Row_Number corrisponda a un indice. Altrimenti le prestazioni si ridurranno allo stesso livello del primo esempio.
Questo approccio richiede comunque un'operazione lineare per generare il cross-table non persistente, ma poiché si tratta solo di un indice con un numero di riga aggiunto, avviene molto rapidamente. Nel mio caso ci sono voluti 0,347 secondi, ma il mio caso aveva varchars che dovevano essere copiati. Un singolo indice numerico richiederebbe molto meno tempo.
Per tutti gli scopi pratici, questo design riduce il ridimensionamento del paging lato server da un'operazione lineare a un'operazione logaritmica consentendo il ridimensionamento di tabelle di grandi dimensioni. Di seguito è la soluzione completa.
-- For a sproc, make these your input parameters
declare
@PageSize int = 50,
@Page int = 15619;
-- For a sproc, make these your output parameters
declare @RecordCount int = (select count(*) from MyTable);
declare @PageCount int = ceiling(convert(float, @RecordCount)/@PageSize);
declare @Offset int = (@Page - 1) * @PageSize;
declare @LowestRowNumber int = @Offset;
declare @HighestRowNumber int = @Offset + @PageSize - 1;
select
@RecordCount as RecordCount,
@PageCount as PageCount,
@Offset as Offset,
@LowestRowNumber as LowestRowNumber,
@HighestRowNumber as HighestRowNumber;
select *
from
(
select
Row_Number() over (order by Field1 asc, Field2 asc, Field3 asc) as RowNumber,
ClusteredIndexField
from MyTable
) as PagedTable
left join MyTable on MyTable.ClusteredIndexField = PagedTable.ClusteredIndexField
where RowNumber between @LowestRowNumber and @HighestRowNumber;
Vedi anche http://stackoverflow.com/questions/216673/emulate-mysql-limit-clause-in-microsoft- sql-server-2000 –
Quale versione di SQL stai usando? Questo è molto più semplice in SQL2005 + – JohnFx
In realtà ... Quando si mette la parolina "efficiente" lì ... non c'è. Persino MySQL, che supporta 'LIMIT N, M' può rallentare in modo orribile sulle ultime" pagine "di tabelle grandi rispetto alle prime pagine. L'unica cosa vicina all'efficienza è se puoi usare l'ID o qualche altro indice per pre-limitare la query a un sottoinsieme di righe. Potrebbe essere utile mappare le pagine su intervalli di ID o data/ora in una pre-query. (tutte le pagine o gruppi di pagine più grandi, calcolati tutti in una volta anziché su ogni pagina) –