2013-01-15 14 views
10

Ho un database con più di 2 milioni di record e ho bisogno di eseguire una paginazione per mostrare sulla mia applicazione web, che deve avere 10 record per pagina in un DataGrid.Qual è il modo migliore per eseguire l'impaginazione su SQL Server?

Ho già provato a utilizzare ROW_NUMBER(), ma in questo modo selezionerò tutti i 2 milioni di record e otterremo solo 10 record. Ho anche provato ad usare TOP 10, ma dovrei salvare il primo e l'ultimo id per controllare le pagine. E ho letto che l'utilizzo di DataAdapter.Fill() selezionerà tutto il contenuto e quindi otterrà i 10 record di cui ho bisogno.

Qual è il modo migliore? Dovrei usare DataAdapter.Fill()? O utilizzare la funzione di SQL Server ROW_NUMBER()? O prova ad usare TOP 10?

+4

vedere questo http://blog.sqlauthority.com/2010/12/15/sql-server-server-side-paging-in-sql-server-2011-a-better-alternative/ –

+0

Basta leggere questo: http://www.asp.net/web-forms/tutorials/data-access/paging-and-sorting/efficiently-paging-through-large-amounts-of-data-vb È un tutorial molto completo (VB.NET ma non importa in questo caso). –

+0

@Guilherme stai * davvero * usando ancora sql-server-2000? (tag) se è così, che limiterà un po 'le tue opzioni. –

risposta

4
ALTER PROCEDURE [dbo].[SP_tblTest_SelectSpecificRecordsWithCTE] 
    @FromRow int = 1000000, 
    @PgSize int = 10 
AS 
BEGIN 
    ;WITH RecordsRN AS 
    (
     select ID, colValue, ROW_NUMBER() over(order by colvalue) as Num from tblTest 
    ) 
    SELECT ID Value, colValue Text FROM RecordsRN WHERE Num between @FromRow AND (@[email protected]) 
END 

è la query che sto utilizzando per il paging. usalo e otterrai i 10 record desiderati in 4-5 secondi. sto ricevendo 10 record in 3 secondi e il totale dei record nel mio db sono 10 milioni, non uso la top 10 e porterò sempre gli stessi 10 record ogni volta. nel mio caso sto mantenendo la dimensione della pagina e il numero di riga iniziale (@FromRow) nella sessione e passo questi due valori alla procedura memorizzata sotto riportata e ottengo il risultato. Inoltre, se si utilizza SQL 2012, è possibile utilizzare OFFSET e recuperare il tipo di cose successivo a 10 righe. cerca su google la parola chiave OFFSET e vedrai il risultato desiderato in cima.

grazie

+0

Tutte le risposte sono davvero belle, ma la tua è più simile a quello che ho fatto, e continuo a pensare che sia il modo migliore. Purtroppo, il mio SQL Server è il 2008, quindi non ho l'istruzione OFFSET da utilizzare. Grazie. –

1

Io uso il seguente schema per (automaticamente) generare sottointerrogazioni paging:

select top (@takeN) <your-column-list> 
from (
    select qSub2.*, _row=row_number() over (order by SomeColumn Asc, SomethingElse Desc) 
    from (
     select top (@takeN + @skipN) <your-column-list> 
     from ( 
      select <your-subquery-here> 
     ) as qSub1 
     order by SomeColumn Asc, SomethingElse Desc 
    ) as qSub2 
) qSub3 
where _row > @skipN 
order by _row 

Note su questo modello:

  • Sotto una query Salta concettualmente @skipN righe e poi prende le successive @takeN righe.
  • Se non ti interessa la colonna aggiuntiva _row nel risultato, è possibile sostituire <your-column-list> con *; Io uso l'elenco di colonne esplicito perché mi consente di suddividere l'insieme di colonne in fase di esecuzione che può essere utile ad es. per trovare solo colonne chiave di prima scelta e simili.
  • Le vostre clausole order by devono essere identiche; Optmizer del server sql è in genere abbastanza intelligente da capirlo. La duplicazione è un effetto collaterale della clausola top utilizzata per troncare i risultati; top non è legale su sottoquery non ordinati. E in alto è utile per aiutare il ottimizzatore di query a capire che questa query è in grado di restituire poche righe.
  • I motivi per utilizzare @takeN e @skipN in contrasto con il numero di pagina + i parametri basati sulle dimensioni sono abbastanza lievi. In primo luogo, è un po 'più flessibile e un po' più semplice nella query, e in secondo luogo, gioca ai punti di forza dei server SQL un po 'meglio: il DB non è particolarmente brillante nell'ottimizzare questo tipo di query in primo luogo, e la speranza è che una clausola superiore semplice e semplice come questa rende banale che l'ottimizzatore capisca il numero massimo di righe possibili. In generale, cerco di evitare di fare calcoli in sql Potrei fare altrettanto bene nel codice poiché tende a confondere l'ottimizzatore (sebbene nel caso specifico di @ pagecount * @ la sperimentazione di pagine ha dimostrato che non è un problema enorme)

Si noti che SQL Server 2012 supporta un nuovo offset...fetch clause proprio per questo scenario, che è molto più semplice.

2

Utilizzare ROW_NUMBER() e implementare una funzione di utilità statica (come GetPaginatedSQL nel mio codice), che avvolge automaticamente la query SQL originale in una limitata/impaginata.

Questo è quello che uso:

namespace Persistence.Utils 
{ 
    public class SQLUtils 
    { 
     /// <summary> 
     /// Builds a paginated/limited query from a SELECT SQL. 
     /// </summary> 
     /// <param name="startRow">Start row</param> 
     /// <param name="numberOfRows">Number/quatity of rows to be expected</param> 
     /// <param name="sql">Original SQL (without its ordering clause)</param> 
     /// <param name="orderingClause">MANDATORY: ordering clause (including ORDER BY keywords)</param> 
     /// <returns>Paginated SQL ready to be executed.</returns> 
     /// <remarks>SELECT keyword of original SQL must be placed exactly at the beginning of the SQL.</remarks> 
     public static string GetPaginatedSQL(int startRow, int numberOfRows, string sql, string orderingClause) 
     { 
      // Ordering clause is mandatory! 
      if (String.IsNullOrEmpty(orderingClause)) 
       throw new ArgumentNullException("orderingClause"); 

      // numberOfRows here is checked of disable building paginated/limited query 
      // in case is not greater than 0. In this case we simply return the 
      // query with its ordering clause appended to it. 
      // If ordering is not spe 
      if (numberOfRows <= 0) 
      { 
       return String.Format("{0} {1}", sql, orderingClause); 
      } 
      // Extract the SELECT from the beginning. 
      String partialSQL = sql.Remove(0, "SELECT ".Length); 

      // Build the limited query... 
      return String.Format(
       "SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) AS rn, {1}) AS SUB WHERE rn > {2} AND rn <= {3}", 
       orderingClause, 
       partialSQL, 
       startRow.ToString(), 
       (startRow + numberOfRows).ToString() 
      ); 
     } 
    } 
} 

La funzione di cui sopra potrebbe essere migliorata, ma è una prima applicazione.

Poi, nei vostri DAO, si dovrebbe essere solo fare qualcosa di simile:

using (var conn = new SqlConnection(CONNECTION_STRING)) 
{ 
    using (var cmd = conn.CreateCommand()) 
    { 
     String SQL = "SELECT * FROM MILLIONS_RECORDS_TABLE"; 
     String SQLOrderBy = "ORDER BY DATE ASC "; //GetOrderByClause(Object someInputParams); 
     String limitedSQL = GetPaginatedSQL(0, 50, SQL, SQLOrderBy); 

     DataSet ds = new DataSet(); 
     SqlDataAdapter adapter = new SqlDataAdapter(); 

     cmd.CommandText = limitedSQL; 

     // Add named parameters here to the command if needed... 

     adapter.SelectCommand = cmd; 
     adapter.Fill(ds); 

     // Process the dataset... 
    } 
    conn.Close(); 
} 

Spero che aiuta.

Problemi correlati