LIMIT 10..20 dans SQL Server

161

J'essaye de faire quelque chose comme:

SELECT * FROM table LIMIT 10,20

ou

SELECT * FROM table LIMIT 10 OFFSET 10

mais en utilisant SQL Server

La seule solution que j'ai trouvée semble exagérée:

SELECT * FROM ( 
  SELECT *, ROW_NUMBER() OVER (ORDER BY name) as row FROM sys.databases 
 ) a WHERE row > 5 and row <= 10

J'ai aussi trouvé :

SELECT TOP 10 * FROM stuff; 

... mais ce n'est pas ce que je veux faire car je ne peux pas spécifier la limite de départ.

Y a-t-il une autre façon pour moi de faire ça?

Aussi, juste curieux, y a-t-il une raison pour laquelle SQL Server ne prend pas en charge la LIMITfonction ou quelque chose de similaire? Je ne veux pas être méchant, mais cela ressemble vraiment à quelque chose dont un SGBD a besoin ... Si c'est le cas, je suis désolé d'être si ignorant! Je travaille avec MySQL et SQL + depuis 5 ans donc ...

Marcgg
la source
1
Utiliser un CTE pour ROW_NUMBER()et limiter avec TOPpour la largeur de la plage et une WHEREcondition pour une limite de la plage est le meilleur que j'ai pu réaliser. J'ai également remarqué de bien meilleures performances si la TOPclause utilise un littéral au lieu d'une variable
Jodrell
Le problème avec toute solution impliquant le ROW_NUMBER () est que si vous ne savez pas à l'avance quelles colonnes vous aurez, et que vous avez des jointures, et que les tables jointes ont le même nom de colonne, vous obtiendrez un "La colonne «xxx» a été spécifié plusieurs fois ". Ce n'est pas aussi rare que cela puisse paraître au départ. J'utilise Dapper et mes tables ont toutes une colonne Id. Dapper se divise et mappe sur cela, donc je ne veux pas les renommer, mais je ne peux pas utiliser l'alias SELECT * FROM ([requête d'origine]). Je n'ai pas encore trouvé de solution!
Steve Owen

Réponses:

104

La LIMITclause ne fait pas partie du SQL standard. Il est pris en charge en tant qu'extension de fournisseur de SQL par MySQL, PostgreSQL et SQLite.

D'autres marques de base de données peuvent avoir des fonctionnalités similaires (par exemple TOPdans Microsoft SQL Server), mais celles-ci ne fonctionnent pas toujours de la même manière.

Il est difficile d'utiliser TOPdans Microsoft SQL Server pour imiter la LIMITclause. Il y a des cas où cela ne fonctionne tout simplement pas.

La solution que vous avez présentée en utilisant ROW_NUMBER()est disponible dans Microsoft SQL Server 2005 et versions ultérieures. C'est la meilleure solution (pour l'instant) qui fonctionne uniquement dans le cadre de la requête.

Une autre solution consiste à utiliser TOPpour récupérer le premier lignes de comptage + décalage , puis à utiliser l'API pour rechercher au-delà des premières lignes de décalage .

Voir également:

Bill Karwin
la source
135

Pour SQL Server 2012 +, vous pouvez utiliser .

SELECT  *
FROM     sys.databases
ORDER BY name 
OFFSET  5 ROWS 
FETCH NEXT 5 ROWS ONLY 
Martin Smith
la source
10
SQl Server 2012 nécessite de spécifier ORDER BY lorsque vous utilisez OFFSET 5 ROWS FETCH NEXT 5 ROWS UNIQUEMENT alors que MySql et SQLite ne nécessitent pas ORDER BY lorsque vous utilisez LIMIT 5,5
Tomas Kubes
4
@ qub1n - MySQL ne garantit pas les lignes que vous récupérez dans ce cas.
Martin Smith
3
Devez-vous utiliser offsetou pouvez-vous laisser cette ligne de côté (en supposant que vous ne vouliez pas de décalage)?
Cullub
1
@Cullub - La clause OFFSET est obligatoire avec FETCH. Vous ne pouvez jamais utiliser, COMMANDER PAR… FETCH. - donc vous avez besoinOFFSET 0 ROWS
Martin Smith
Votre exemple de requête fonctionne bien mais si je change le nom de la table et la commande par col comme ci-dessous SELECT * FROM DimProduct ORDER BY ProductKey OFFSET 5 ROWS FETCH NEXT 5 ROWS ONLY Cela donne une erreurParse error at line: 4, column: 1: Incorrect syntax near 'OFFSET'
shashwat
36

comme vous l'avez trouvé, c'est la méthode de serveur sql préférée:

SELECT * FROM ( 
  SELECT *, ROW_NUMBER() OVER (ORDER BY name) as row FROM sys.databases 
 ) a WHERE a.row > 5 and a.row <= 10
KM.
la source
Pourquoi aaprès la sélection intérieure? Je suppose que vous donnez un alias à la sélection interne, mais vous ne semblez jamais l'utiliser ... Devriez-vous le faire a.rowau lieu de simplement row?
Lucas
3
@Lucas, vous êtes obligé de mettre un alias après la ( )table dérivée, mais il la laissera aller si vous oubliez ensuite de l'utiliser pour faire référence aux colonnes. Je l'ai réparé cependant ...
KM.
merci, j'ai découvert cela à la dure (j'ai essayé de laisser l'alias de côté).
Lucas
1
Voté +1: Cependant, la réponse de @MartinSmith est votée davantage, après avoir comparé le plan d'exécution avec celui de cette approche, j'ai découvert que cette solution fonctionne beaucoup plus rapidement.
Harsh
10

Si vous utilisez SQL Server 2012+, votez pour la réponse de Martin Smith et utilisez les extensions OFFSETet FETCH NEXTpour ORDER BY,

Si vous avez la malchance d'être coincé avec une version antérieure, vous pouvez faire quelque chose comme ça,

WITH Rows AS
(
    SELECT
              ROW_NUMBER() OVER (ORDER BY [dbo].[SomeColumn]) [Row]
            , *
        FROM
              [dbo].[SomeTable]
)
SELECT TOP 10
          *
     FROM
         Rows
    WHERE Row > 10

Je crois que c'est fonctionnellement équivalent à

SELECT * FROM SomeTable LIMIT 10 OFFSET 10 ORDER BY SomeColumn

et la manière la plus performante que je connaisse de le faire dans TSQL, avant MS SQL 2012.


S'il y a beaucoup de lignes, vous pouvez obtenir de meilleures performances en utilisant une table temporaire au lieu d'un CTE.

Jodrell
la source
Voté pour avoir souligné la réponse de Martin Smith (et un lien vers elle) tout en fournissant une solution pré-2012. Aussi pour les conseils de la table temporaire car vous avez raison :)
fujiiface
7

Malheureusement, ROW_NUMBER()c'est le mieux que vous puissiez faire. C'est en fait plus correct, car les résultats d'une clause limitou topn'ont pas vraiment de sens sans un ordre spécifique. Mais c'est toujours une douleur à faire.

Mise à jour: Sql Server 2012 ajoute une limitfonctionnalité similaire via les mots-clés OFFSET et FETCH . C'est l'approche standard ansi, par opposition à LIMIT, qui est une extension MySql non standard.

Joël Coehoorn
la source
@Joel: Pouvez-vous expliquer pourquoi ROW_NUMBER () est incapable de numéroter les lignes comme elles sortent de ORDER BY? Je me suis toujours demandé pourquoi le "OVER (ORDER BY name)" était obligatoire, mais je suppose qu'il y a une bonne raison à cela. Ou du moins une raison.
Tomalak
3
car il n'y a pas d'ordre sans ordre par article. Vous obtenez quel que soit l'ordre dans lequel les enregistrements étaient disponibles pour le serveur, et cela pourrait changer d'une requête à une requête.
Joel Coehoorn
1
@marcgg: Je n'ai jamais lu aucune indication que Microsoft envisage d'implémenter LIMIT. Même s'ils ont un tel plan, les fournisseurs à source fermée ont tendance à ne pas pré-annoncer les fonctionnalités. Ce serait certainement une fonctionnalité utile, mais nous ne savons pas combien de travail ce serait à implémenter, étant donné leur code.
Bill Karwin
3
Si vous ne souhaitez pas vous répéter dans la clause ORDER BY, utilisez l'alias ROW_NUMBER () plutôt que l'ensemble de colonnes d'origine.
Peter Radocchia
2
@Tomalak: En ce qui concerne SQL Server, l'ordre utilisé pour calculer ROW_NUMBER () est totalement indépendant de l'ordre du jeu de résultats. C'est pourquoi vous devez les spécifier séparément.
LukeH
6

Que dis-tu de ça?

SET ROWCOUNT 10 

SELECT TOP 20 *
FROM sys.databases
ORDER BY database_id DESC

Il vous donne les 10 dernières lignes des 20 premières lignes. Un inconvénient est que l'ordre est inversé, mais au moins c'est facile à retenir.

David Patrick
la source
6
Et s'il n'y a que 14 lignes dans le tableau? Vous obtenez les lignes 14 à 5, ce qui n'est pas la même chose que les lignes renvoyées par LIMIT 10 OFFSET 10 (devrait être des lignes 14 à 11).
Bill Karwin le
2
SELECT TOP 10 *
FROM TABLE
WHERE IDCOLUMN NOT IN (SELECT TOP 10 IDCOLUMN FROM TABLE)

Devrait donner des enregistrements 11-20. Probablement pas trop efficace si vous augmentez pour obtenir d'autres pages, et ne savez pas comment cela pourrait être affecté par la commande. Il se peut que vous deviez le spécifier dans les deux instructions WHERE.

Andy
la source
1

Un bon moyen est de créer une procédure:

create proc pagination (@startfrom int ,@endto int) as
SELECT * FROM ( 
  SELECT *, ROW_NUMBER() OVER (ORDER BY name desc) as row FROM sys.databases 
 ) a WHERE a.row > @startfrom and a.row <= @endto

tout comme limite 0,2 /////////////// exécuter la pagination 0,4

Wahaj Latif
la source
1

Juste pour la solution d'enregistrement qui fonctionne sur la plupart des moteurs de base de données, mais peut-être pas la plus efficace:

Select Top (ReturnCount) *
From (
    Select Top (SkipCount + ReturnCount) *
    From SourceTable
    Order By ReverseSortCondition
) ReverseSorted
Order By SortCondition

Remarque Pelase: la dernière page contiendrait toujours des lignes ReturnCount, quel que soit SkipCount. Mais cela pourrait être une bonne chose dans de nombreux cas.

YB
la source
1

L'équivalent de LIMIT est SET ROWCOUNT, mais si vous voulez une pagination générique, il est préférable d'écrire une requête comme celle-ci:

;WITH Results_CTE AS
(
    SELECT
        Col1, Col2, ...,
        ROW_NUMBER() OVER (ORDER BY SortCol1, SortCol2, ...) AS RowNum
    FROM Table
    WHERE <whatever>
)
SELECT *
FROM Results_CTE
WHERE RowNum >= @Offset
AND RowNum < @Offset + @Limit
Sonker Satish Kumar
la source
0
select * from (select id,name,ROW_NUMBER() OVER (ORDER BY id  asc) as row
from tableName1) tbl1
where tbl1.row>=10 and tbl1.row<=15

Imprime les lignes de 10 à 15.

sjith
la source
0

Jusqu'à présent, ce format est ce qui fonctionne pour moi (pas la meilleure performance cependant):

SELECT TOP {desired amount of rows} * 
FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY {order columns} asc)__row__ FROM {table})tmp
WHERE __row__ > {offset row count}

Une note sur le côté, paginer sur des données dynamiques peut conduire à des résultats étranges / inattendus.

Charlie Affumigato
la source
0

De la documentation en ligne de MS SQL Server ( http://technet.microsoft.com/en-us/library/ms186734.aspx ), voici leur exemple que j'ai testé et fonctionne, pour récupérer un ensemble spécifique de lignes. ROW_NUMBER nécessite un OVER, mais vous pouvez commander ce que vous voulez:

WITH OrderedOrders AS
(
  SELECT SalesOrderID, OrderDate,
  ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNumber
  FROM Sales.SalesOrderHeader 
) 
SELECT SalesOrderID, OrderDate, RowNumber  
FROM OrderedOrders 
WHERE RowNumber BETWEEN 50 AND 60;
Shannon WM
la source
0

Utilisez tout le serveur SQL:; avec tbl as (SELECT ROW_NUMBER () over (order by (select 1)) as RowIndex, * from table) select top 10 * from tbl where RowIndex> = 10

Phạm Tấn Lợi
la source
-3
 SELECT * FROM users WHERE Id Between 15 and 25

il imprimera de 15 à 25 comme limite dans MYSQl

SR1
la source
2
Et si l'utilisateur supprimait un enregistrement entre 15 et 25?
Gökçer Gökdal