2009-04-16 12 views
60

C'è un modo semplice per ottenere migliori N righe da qualsiasi tabella:Come arrivare N righe a partire dal braccio della M dalla tabella ordinata in T-SQL

SELECT TOP 10 * FROM MyTable ORDER BY MyColumn 

Esiste un modo efficace per interrogare m righe di partenza dalla fila N

Ad esempio,

Id Value 
1 a 
2 b 
3 c 
4 d 
5 e 
6 f 

E query simile

SELECT [3,2] * FROM MyTable ORDER BY MyColumn /* hypothetical syntax */ 

interroga 2 righe a partire da una riga 3D, quindi le righe 3d e 4 vengono restituite.

+1

Vedi anche http://stackoverflow.com/questions/216673/emulate-mysql-limit-clause-in-microsoft- sql-server-2000 –

+0

Quale versione di SQL stai usando? Questo è molto più semplice in SQL2005 + – JohnFx

+0

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) –

risposta

83

UPDATE Se si utilizza SQL 2012 nuova sintassi è stato aggiunto per rendere il tutto veramente facile. Vedo Implement paging (skip/take) functionality with this query

Credo che il più elegante è quello di utilizzare la funzione di ROW_NUMBER (disponibile da MS SQL Server 2005):

WITH NumberedMyTable AS 
(
    SELECT 
     Id, 
     Value, 
     ROW_NUMBER() OVER (ORDER BY Id) AS RowNumber 
    FROM 
     MyTable 
) 
SELECT 
    Id, 
    Value 
FROM 
    NumberedMyTable 
WHERE 
    RowNumber BETWEEN @From AND @To 
+3

Se si hanno istruzioni select prima di 'WITH', si dovrà terminare l'istruzione precedente con un punto e virgola (sostituire in modo efficace' WITH' con '; WITH') – TodK

+1

Questo potrebbe essere correlato e corrente per SQL Server 2012: [Salto T-SQL prende la stored procedure] (http://stackoverflow.com/a/5620802/456814). –

+0

Non è stato restituito alcun record per me –

5

Ugly, hacker, ma dovrebbe funzionare:

select top(M + N - 1) * from TableName 
except 
select top(N - 1) * from TableName 
+1

sì, ci ho pensato. Non penso che funzionerà su grande set di dati in modo efficiente. Ma non ho nemmeno altre idee. – inazaruk

+0

-Grazie per questo. Questo è utile quando la tabella non ha un indice e non è possibile modificare i metadati della tabella. – Farjad

2
@start = 3 
@records = 2 

Select ID, Value 
From 
(SELECT ROW_NUMBER() OVER(ORDER BY ID) AS RowNum, ID,Value 
From MyTable) as sub 
Where sub.RowNum between @start and @[email protected] 

Questo è un modo. ce ne sono molti altri se cerchi google SQL.

0

Cerca un ID per la riga N quindi ottenere il file top M che dispongono di un id maggiore o uguale a quella

 
declare @N as int 
set @N = 2 
declare @M as int 
set @M = 3 

declare @Nid as int 

set @Nid = max(id) 
from 
    (select top @N * 
from MyTable 
order by id) 

select top @M * 
from MyTable 
where id >= @Nid 
order by id 

Qualcosa di simile ... ma ho fatto alcune ipotesi qui (ad esempio, vuoi ordinare per id)

0

C'è un metodo piuttosto diretto per T-SQL, anche se non sono sicuro se è di prestigio-efficace se stai saltando un numero elevato di righe.

SELECT TOP numberYouWantToTake 
    [yourColumns...] 
FROM yourTable 
WHERE yourIDColumn NOT IN (
    SELECT TOP numberYouWantToSkip 
     yourIDColumn 
    FROM yourTable 
    ORDER BY yourOrderColumn 
) 
ORDER BY yourOrderColumn 

Se stai usando .NET, è possibile utilizzare il seguente su per esempio un IEnumerable con i risultati dati:

IEnumerable<yourDataType> yourSelectedData = yourDataInAnIEnumerable.Skip(nubmerYouWantToSkip).Take(numberYouWantToTake); 

Questo ha il didietro che state ottenendo tutti i dati da l'archiviazione dei dati.

0

Perché non fare due query:

select top(M+N-1) * from table into temp tmp_final with no log; 
select top(N-1) * from tmp_final order by id desc; 
8

Se si desidera selezionare 100 record dal record di 25:

select TOP 100 * from TableName 
where PrimaryKeyField 
    NOT IN(Select TOP 24 PrimaryKeyField from TableName); 
+1

Ciò non funzionerebbe se ho una chiave primaria non intera o una chiave primaria composita nella tabella. –

+2

A seconda della quantità di dati restituiti, questa potrebbe non essere la soluzione migliore per le grandi query – Shide

+1

per motivi di prestazioni che non è consigliabile per gli ambienti di produzione –

1

In seguito è la semplice query elencherà N righe da M + 1 ° fila di la tavola. Sostituisci M e N con i tuoi numeri preferiti.

Select Top N B.PrimaryKeyColumn from 
    (SELECT 
     top M PrimaryKeyColumn 
    FROM 
     MyTable 
) A right outer join MyTable B 
on 
    A.PrimaryKeyColumn = B.PrimaryKeyColumn 
where 
    A.PrimaryKeyColumn IS NULL 

Per favore fatemi sapere se questo è utile per la vostra situazione.

3

probabilmente un bene per i piccoli risultati, funziona in tutte le versioni di TSQL:

SELECT 
     * 
FROM 
    (SELECT TOP (N) * 
     FROM 
      (SELECT TOP (M + N - 1) 
      FROM 
        Table 
      ORDER BY 
         MyColumn) qasc 
     ORDER BY 
       MyColumn DESC) qdesc 
ORDER BY 
     MyColumn 
+0

Vedere anche lieve modifica nella [domanda collegata da @BillKarwin, sopra] (http://stackoverflow.com/questions/216673/emulate-mysql-limit-clause-in-microsoft-sql-server-2000) per quando la tabella ha meno di M + N-1 righe. – ruffin

1

e in questo modo si può raggiungere lo stesso obiettivo sulle tabelle senza chiave primaria:

select * from 
(
    select row_number() over(order by (select 0)) rowNum,* 
    from your_table 
) tmp 
where tmp.rowNum between 20 and 30 -- any numbers you need 
3
 -- *some* implementations may support this syntax (mysql?) 
SELECT Id,Value 
FROM xxx 
ORDER BY Id 
LIMIT 2 , 0 
    ; 

     -- Separate LIMIT, OFFSET 
SELECT Id,Value 
FROM xxx 
ORDER BY Id 
LIMIT 2 OFFSET 2 
    ; 

     -- SQL-2008 syntax 
SELECT Id,Value 
FROM xxx 
ORDER BY Id 
OFFSET 4 
FETCH NEXT 2 ROWS ONLY 
    ; 
+1

OFFSET/LIMIT sono stati aggiunti a SQL Server 2012, pertanto non funzioneranno nel 2008. – CodeNaked

+0

Nell'OP non viene menzionato nulla chiamato "server 2012". Si prega di considerare questa una risposta solo SQL. – wildplasser

+0

Scusate, stavo appena uscendo dal vostro commento sopra quella sezione di codice (ad esempio 'sintassi SQL-2008'). Supponevo che intendessi SQL Server 2008. – CodeNaked

0
SELECT * FROM (
    SELECT 
    Row_Number() Over (Order by (Select 1)) as RawKey, 
    * 
    FROM [Alzh].[dbo].[DM_THD_TRANS_FY14] 
) AS foo 
WHERE RawKey between 17210400 and 17210500 
14

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; 
+2

Addendum ...Ho testato questo approccio con una tabella che ha 13.200 record e ha utilizzato una dimensione della pagina di 50 risultante in 264.017 pagine. L'accesso alla pagina 1 è durato 19 secondi. L'accesso all'ultima pagina è durato 1 minuto e 11 secondi. Sebbene questo sia ancora un grande miglioramento rispetto agli altri metodi, non è sufficiente per una risposta in tempo reale. Per superare questo problema di prestazioni, aggiungi una "N superiore" alla selezione interna. Ciò riduce il tempo necessario per ottenere qualsiasi pagina a scapito di limitare i risultati totali a N. È inoltre possibile aggiungere eventuali parametri di filtro alla clausola where della inner select. –

+0

Come posizionare una "N superiore" all'interno del lavoro di selezione interno. Ho provato questo su una delle mie query, ma limiterà semplicemente il tuo cross-table e qualsiasi intervallo al di fuori della tua "top N" non verrà restituito. Ho frainteso qualcosa? – Ian

1

Ho letto tutte le risposte qui e alla fine ho trovato una soluzione utilizzabile che è semplice. I problemi di prestazioni derivano dall'istruzione BETWEEN, non dalla generazione dei numeri di riga stessi. Quindi ho usato un algoritmo per fare il paging dinamico passando il numero di pagina e il numero di record.

I passaggi non sono la riga iniziale e il numero di righe, ma piuttosto "righe per pagina (500)" e "numero pagina (4)" che sarebbero le righe 1501 - 2000. Questi valori possono essere sostituiti da variabili stored procedure quindi non sei obbligato a utilizzare una specifica quantità di paging.

select * from (
    select 
     (((ROW_NUMBER() OVER(ORDER BY MyField) - 1)/500) + 1) AS PageNum 
     , * 
    from MyTable 
) as PagedTable 
where PageNum = 4; 
8

In SQL 2012 è possibile utilizzare OFFSET e FETCH:

SELECT * 
FROM MyTable 
ORDER BY MyColumn 
OFFSET @N ROWS 
FETCH NEXT @M ROWS ONLY; 


Io personalmente preferisco:

DECLARE @CurrentSetNumber int = 0; 
DECLARE @NumRowsInSet int = 2; 

SELECT * 
FROM MyTable 
ORDER BY MyColumn 
OFFSET @NumRowsInSet * @CurrentSetNumber ROWS 
FETCH NEXT @NumRowsInSet ROWS ONLY; 

SET @CurrentSetNumber = @CurrentSetNumber + 1; 

dove @NumRowsInSet è il numero di righe che si desidera venga restituito e @CurrentSetNumber è il numero di @NumRowsInSet da saltare.

1

Per fare questo in SQL Server, è necessario ordinare la query una colonna, quindi è possibile specificare le righe che si desidera.

Non è possibile utilizzare la parola chiave "TOP" quando si esegue questa operazione, è necessario utilizzare le righe N offset recuperare le righe M successive.

Esempio:

select * from table order by [some_column] 
offset 10 rows 
FETCH NEXT 10 rows only 

Potete saperne di più qui: https://technet.microsoft.com/pt-br/library/gg699618%28v=sql.110%29.aspx

Problemi correlati