2008-10-09 7 views
120

Existe-t-il un moyen dans SQL Server d'obtenir les résultats à partir d'un décalage donné? Par exemple, dans un autre type de base de données SQL, il est possible de le faire:Décalage de ligne dans SQL Server

SELECT * FROM MyTable OFFSET 50 LIMIT 25 

pour obtenir des résultats 51-75. Cette construction ne semble pas exister dans SQL Server.

Comment puis-je accomplir ceci sans charger toutes les lignes dont je ne me soucie pas? Merci!

Répondre

143

J'éviter d'utiliser SELECT *. Spécifiez les colonnes que vous voulez réellement même si elles sont toutes.

SQL Server 2005+

SELECT col1, col2 
FROM (
    SELECT col1, col2, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum 
    FROM MyTable 
) AS MyDerivedTable 
WHERE MyDerivedTable.RowNum BETWEEN @startRow AND @endRow 

SQL Server 2000

Efficiently Paging Through Large Result Sets in SQL Server 2000

A More Efficient Method for Paging Through Large Result Sets

+5

Pourquoi suggérez-vous d'éviter SELECT même si vous sélectionnez toutes les colonnes? –

+8

Je suis sûr qu'il a utilisé "*" parce qu'il était plus simple de taper et de mieux faire passer le point que "col1, col2, ... colN" – rotard

+6

Quant à savoir pourquoi ne pas l'utiliser, 'SELECT *' signifie que si le structure de la table change, votre requête fonctionne toujours, mais donne des résultats différents. Si une colonne est ajoutée, cela peut être utile (bien que vous deviez toujours l'utiliser par nom quelque part); Si une colonne est supprimée ou renommée, il vaut mieux que votre SQL se casse visiblement que le code se comporte bizarrement, car une variable n'est pas initialisée. – IMSoP

2

En fonction de votre version OU ne peut le faire directement, mais vous pouvez faire quelque chose comme hacky

select top 25 * 
from ( 
    select top 75 * 
    from table 
    order by field asc 
) a 
order by field desc 

où « champ » est la clé.

+4

Cette solution pour SQL2000 ne fonctionne pas pour la dernière page du jeu de résultats, à moins que le nombre total de lignes ne soit un multiple de la taille de la page. –

21

C'est une façon (SQL2000)

SELECT * FROM 
(
    SELECT TOP (@pageSize) * FROM 
    (
     SELECT TOP (@pageNumber * @pageSize) * 
     FROM tableName 
     ORDER BY columnName ASC 
    ) AS t1 
    ORDER BY columnName DESC 
) AS t2 
ORDER BY columnName ASC 

ce qui est une autre façon (SQL 2005)

;WITH results AS (
    SELECT 
     rowNo = ROW_NUMBER() OVER(ORDER BY columnName ASC) 
     , * 
    FROM tableName 
) 
SELECT * 
FROM results 
WHERE rowNo between (@pageNumber-1)*@pageSize+1 and @pageNumber*@pageSize 
+0

Juste pour clarifier sur le premier ... (@pageSize) est un espace réservé ici pour la valeur réelle. Vous devrez faire le 'TOP 25' spécifiquement; SQL Server 2000 ne prend pas en charge les variables dans une clause TOP. Cela en fait une douleur impliquant le SQL dynamique. – Cowan

+5

Cette solution pour SQL2000 ne fonctionne pas pour la dernière page du jeu de résultats, à moins que le nombre total de lignes ne soit un multiple de la taille de la page. –

10

Vous pouvez utiliser la fonction ROW_NUMBER() pour obtenir ce que vous voulez:

SELECT * 
FROM (SELECT ROW_NUMBER() OVER(ORDER BY id) RowNr, id FROM tbl) t 
WHERE RowNr BETWEEN 10 AND 20 
1

En SQLServer2005 vous pouvez effectuer les opérations suivantes:

DECLARE @Limit INT 
DECLARE @Offset INT 
SET @Offset = 120000 
SET @Limit = 10 

SELECT 
    * 
FROM 
(
    SELECT 
     row_number() 
    OVER 
     (ORDER BY column) AS rownum, column2, column3, .... columnX 
    FROM 
    table 
) AS A 
WHERE 
A.rownum BETWEEN (@Offset) AND (@Offset + @Limit-1) 
+0

Cela ne devrait-il pas être '@Offset + @Limit - 1'? Si @Limit vaut 10, cela retournera 11 lignes. –

+0

Vous avez raison. Je vais modifier ma réponse pour refléter votre point de vue. – Aheho

2

Vous devez être prudent lorsque vous utilisez l'instruction row_number() OVER (ORDER BY) comme performane est assez pauvre. Idem pour utiliser Common Table Expressions avec row_number() qui est encore pire. J'utilise l'extrait suivant qui s'est avéré être légèrement plus rapide que l'utilisation d'une variable de table avec une identité pour fournir le numéro de page.

DECLARE @Offset INT = 120000 
DECLARE @Limit INT = 10 

DECLARE @ROWCOUNT INT = @[email protected] 
SET ROWCOUNT @ROWCOUNT 

SELECT * FROM MyTable INTO #ResultSet 
WHERE MyTable.Type = 1 

SELECT * FROM 
(
    SELECT *, ROW_NUMBER() OVER(ORDER BY SortConst ASC) As RowNumber FROM 
    (
     SELECT *, 1 As SortConst FROM #ResultSet 
    ) AS ResultSet 
) AS Page 
WHERE RowNumber BETWEEN @Offset AND @ROWCOUNT 

DROP TABLE #ResultSet 
+0

Cela retournera 11 lignes, pas 10. –

88

Si vous traiterez toutes les pages pour ensuite se rappeler simplement la dernière valeur de clé vu sur la page précédente et en utilisant TOP (25) ... WHERE Key > @last_key ORDER BY Key peut être la meilleure méthode d'exécution si les index appropriés existent pour permettre que cela soit seeked efficacement - ou an API cursor si ce n'est pas le cas.

Pour sélectionner une page la meilleure solution arbitraire pour SQL Server 2005 à 2008 R2 est probablement ROW_NUMBER et BETWEEN

Pour SQL Server 2012+ vous pouvez utiliser la clause améliorée ORDER BY pour ce besoin.

SELECT * 
FROM  MyTable 
ORDER BY OrderingColumn ASC 
OFFSET 50 ROWS 
FETCH NEXT 25 ROWS ONLY 

Bien que it remains to be seen how well performing this option will be.

+2

Il est maintenant disponible dans SQL Server Compact 4.0 -> http://msdn.microsoft.com/en-us/library/gg699618(v=sql.110).aspx –

+12

C'est à propos de temps ils ont ajouté ceci à tSQL – JohnFx

+3

Seulement pour Sql Server 2012 :( –

0

J'ai recherché cette réponse pendant un moment maintenant (pour des requêtes génériques) et trouvé une autre façon de le faire sur SQL Server 2000+ en utilisant ROWCOUNT et les curseurs et sans TOP ou une table temporaire. En utilisant le SET ROWCOUNT [OFFSET+LIMIT], vous pouvez limiter les résultats, et avec les curseurs, allez directement à la ligne que vous souhaitez, puis bouclez jusqu'à la fin.

Ainsi, votre requête serait comme ceci:

SET ROWCOUNT 75 -- (50 + 25) 
DECLARE MyCursor SCROLL CURSOR FOR SELECT * FROM pessoas 
OPEN MyCursor 
FETCH ABSOLUTE 50 FROM MyCursor -- OFFSET 
WHILE @@FETCH_STATUS = 0 BEGIN 
    FETCH next FROM MyCursor 
END 
CLOSE MyCursor 
DEALLOCATE MyCursor 
SET ROWCOUNT 0 
+0

Je détesterais voir la performance de ceci quand vous arrivez vers la fin de la table ... –

6

Pour les tables avec de plus en grandes colonnes de données, je préfère:

SELECT 
    tablename.col1, 
    tablename.col2, 
    tablename.col3, 
    ... 
FROM 
(
    (
    SELECT 
     col1 
    FROM 
    (
     SELECT col1, ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum 
     FROM tablename 
     WHERE ([CONDITION]) 
    ) 
    AS T1 WHERE T1.RowNum BETWEEN [OFFSET] AND [OFFSET + LIMIT] 
) 
    AS T2 INNER JOIN tablename ON T2.col1=tablename.col1 
); 

-

[CONDITION] can contain any WHERE clause for searching. 
[OFFSET] specifies the start, 
[LIMIT] the maximum results. 

Il a beaucoup mieux performances sur des tables avec des données volumineuses telles que les objets BLOB, car la fonction ROW_NUMBER ne doit parcourir qu'une seule colonne, et Les lignes correspondantes sont renvoyées avec toutes les colonnes.

1

J'utilise cette technique pour la pagination. Je ne vais pas chercher toutes les lignes. Par exemple, si ma page doit afficher les 100 premières lignes, je ne récupère que la clause 100 avec where. La sortie du SQL doit avoir une clé unique.

Le tableau a les éléments suivants:

ID, KeyId, Rank 

Le même rang sera attribué plus d'un KeyId.

SQL est select top 2 * from Table1 where Rank >= @Rank and ID > @Id

Pour la première fois que je passe 0 pour les deux. La deuxième fois passe 1 & 14. passe 3ème temps 2 et 6 ....

La valeur du 10 dossier Grade & Id est passé à la prochaine

11 21 1 
14 22 1 
7 11 1 
6 19 2 
12 31 2 
13 18 2 

Cela aura moins de stress sur le système

3

Voir ma sélection pour paginator

SELECT TOP @limit * FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY colunx ASC) offset, * FROM (

    -- YOU SELECT HERE 
    SELECT * FROM mytable 


    ) myquery 
) paginator 
WHERE offset > @offset 

Cela résout la mise en page;)

5

Il existe OFFSET .. FETCH dans SQL Server 2012, mais vous devez spécifier une colonne ORDER BY.

Si vraiment vous n'avez pas explicitement colonne que vous pourriez passer comme une colonne ORDER BY (comme d'autres l'ont suggéré), vous pouvez utiliser cette astuce:

SELECT * FROM MyTable 
ORDER BY @@VERSION 
OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY 

... ou

SELECT * FROM MyTable 
ORDER BY (SELECT 0) 
OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY 

Nous l'utilisons dans jOOQ lorsque les utilisateurs ne spécifient pas explicitement une commande. Cela produira alors une commande plutôt aléatoire sans frais supplémentaires.

4
SELECT TOP 75 * FROM MyTable 
EXCEPT 
SELECT TOP 50 * FROM MyTable 
+1

C'est la réponse la plus simple ... – Mai

+2

C'est ce que je –

+0

Performance sage ne semble pas optimale que la requête est ensuite inutilement exécuté deux fois.Surtout que l'utilisateur va vers les pages les plus élevées, la requête pour rejeter les lignes, c'est-à-dire la partie ci-dessous sera plus long et plus long – vanval

2

Après affiche 25 enregistrements hors 50 premiers enregistrements fonctionne dans SQL Server 2012.

SELECT * FROM MyTable ORDER BY ID OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY; 

vous pouvez remplacer ID que votre exigence

+0

, ajoutez ceci est possible dans SQL SERVER 2012 –

0

La meilleure façon de le faire sans perdre de temps à commander dossiers est comme ceci:

select 0 as tmp,Column1 from Table1 Order by tmp OFFSET 5000000 ROWS FETCH NEXT 50 ROWS ONLY 

il faut moins d'une seconde!
meilleure solution pour les grandes tables.

Questions connexes