web-dev-qa-db-fra.com

Comment obtenir N lignes à partir de la ligne M d'une table triée dans T-SQL

Il existe un moyen simple d’obtenir les N premières lignes d’une table:

SELECT TOP 10 * FROM MyTable ORDER BY MyColumn

Existe-t-il un moyen efficace d'interroger M lignes à partir de la ligne N?

Par exemple,

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

Et requête comme ça

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

interroge 2 lignes à partir de la ligne 3d, c’est-à-dire que les lignes 3d et 4 sont renvoyées.

62
inazaruk

UPDATE Si vous utilisez SQL 2012, une nouvelle syntaxe a été ajoutée pour simplifier les choses. Voir Implémenter la fonctionnalité de pagination (sauter/prendre) avec cette requête

Je suppose que le plus élégant consiste à utiliser la fonction ROW_NUMBER (disponible à partir de 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
88
Jan Zich

Le problème avec les suggestions dans ce fil et ailleurs sur le Web est que toutes les solutions proposées fonctionnent en temps linéaire par rapport au nombre d'enregistrements. Par exemple, considérons une requête comme celle-ci.

select *
from
(
    select
        Row_Number() over (order by ClusteredIndexField) as RowNumber,
        *
    from MyTable
) as PagedTable
where RowNumber between @LowestRowNumber and @HighestRowNumber;

Lors de l'obtention de la page 1, la requête prend 0,577 seconde. Toutefois, lors de l'obtention de la page 15 619, cette même requête prend plus de 2 minutes et 55 secondes.

Nous pouvons grandement améliorer cela en créant un numéro d'enregistrement, une table croisée d'index, comme indiqué dans la requête suivante. Le tableau croisé s'appelle PagedTable et n'est pas persistant.

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;

Comme dans l'exemple précédent, j'ai testé cela sur une très grande table avec 780 928 enregistrements. J'ai utilisé une taille de page de 50, ce qui a abouti à 15 619 pages.

Le temps total pris pour la page 1 (la première page) est de 0,413 seconde. Le temps total pris pour la page 15 619 (la dernière page) est de 0,987 secondes, soit deux fois plus que la page 1. Ces temps ont été mesurés à l'aide de SQL Server Profiler et le SGBD était SQL Server 2008 R2.

Cette solution fonctionne dans tous les cas lorsque vous triez votre table par un index. L'index n'a pas besoin d'être en cluster ou simple. Dans mon cas, l'index était composé de trois champs: varchar (50) asc, varchar (15) asc, numérique (19,0) asc. Le fait que les performances aient été excellentes malgré la lourdeur de l’indice montre bien que cette approche fonctionne.

Cependant, il est essentiel que la clause order by de la fonction de fenêtrage Row_Number corresponde à un index. Sinon, les performances se dégradent au même niveau que dans le premier exemple.

Cette approche nécessite toujours une opération linéaire pour générer le tableau croisé non persistant, mais comme il ne s'agit que d'un index avec un numéro de ligne ajouté, cela se produit très rapidement. Dans mon cas, cela a pris 0,347 secondes, mais mon cas avait varchars qui devaient être copiés. Un seul index numérique prendrait beaucoup moins de temps.

À toutes fins pratiques, cette conception réduit la mise à l'échelle de la pagination côté serveur d'une opération linéaire à une opération logarithmique permettant la mise à l'échelle de tables volumineuses. Vous trouverez ci-dessous la solution complète.

-- 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;
16
Daniel Barbalace

Dans SQL 2012 vous pouvez utiliser OFFSET et FETCH:

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


Personnellement, je préfère:

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;

@NumRowsInSet est le nombre de lignes que vous voulez renvoyer et @CurrentSetNumber est le nombre de @NumRowsInSet à ignorer.

10
Trisped

Si vous souhaitez sélectionner 100 enregistrements à partir du 25ème enregistrement:

select TOP 100 * from TableName
where PrimaryKeyField 
   NOT IN(Select TOP 24 PrimaryKeyField from TableName);
8
Prashant

Moche, hackish, mais devrait fonctionner:

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

Probablement bon pour les petits résultats, fonctionne dans toutes les versions de 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
3
Steven Kraninger
        -- *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
  ;
3
wildplasser

Pour ce faire dans SQL Server, vous devez classer la requête par une colonne afin de pouvoir spécifier les lignes souhaitées.

Vous ne pouvez pas utiliser le mot clé "TOP" pour cela. Vous devez utiliser le décalage N lignes pour rechercher les M lignes suivantes.

Exemple:

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

Vous pouvez en apprendre plus ici: https://technet.Microsoft.com/pt-br/library/gg699618%28v=sql.110%29.aspx

2
Felipe V. R.
@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 @start+@records

C'est une façon. il y en a beaucoup d'autres si vous utilisez Google SQL Paging.

2
Jeremy

Ce fil est assez ancien, mais vous pouvez actuellement le faire: Beaucoup plus propre à mon humble avis

SELECT *
FROM Sales.SalesOrderDetail
ORDER BY SalesOrderDetailID
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;
GO

source: http://blog.sqlauthority.com/2013/12/30/sql-server-mysql-limit-and-offset-skip-and-return-next-few-rows-paging-solution/

2
Sander Kouwenhoven

Et voici comment vous pouvez atteindre le même objectif sur des tables sans clé primaire:

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
1

La requête suivante répertorie N lignes de M + 1 ligne du tableau. Remplacez M et N par vos numéros préférés.

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

S'il vous plaît laissez-moi savoir si cela est utile pour votre situation.

1
Bala

J'ai lu toutes les réponses ici et j'ai finalement trouvé une solution simple et utilisable. Les problèmes de performances proviennent de l'instruction BETWEEN et non de la génération des numéros de ligne eux-mêmes. J'ai donc utilisé un algorithme pour effectuer une pagination dynamique en transmettant le numéro de page et le nombre d'enregistrements.

Les passes ne sont ni la ligne de départ ni le nombre de lignes, mais plutôt les "lignes par page (500)" et le "numéro de page (4)", qui correspondent aux lignes 1501 à 2000. Ces valeurs peuvent être remplacées par des variables de procédure stockées. verrouillé en utilisant un montant de pagination spécifique.

select * from (
    select
        (((ROW_NUMBER() OVER(ORDER BY MyField) - 1) / 500) + 1) AS PageNum
        , *
    from MyTable
) as PagedTable
where PageNum = 4;
1
Harley K
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
0
Vinod Kushwaha

Recherchez l'identifiant de la ligne N Puis, obtenez les M premières lignes dont l'identifiant est supérieur ou égal à celui

 déclarer @N en tant qu'int 
 set @N = 2 
 déclarer @M en tant qu'int 
 set @M = 3 

 déclarer @Nid en tant qu'int 

 set @Nid = max (id) 
 À partir de 
 (sélectionnez top @N * 
 dans MyTable 
 par ordre) 

 sélectionnez top @M * 
 dans MyTable 
 où id> = @Nid 
 order by id

Quelque chose comme ça ... mais j'ai fait certaines hypothèses ici (par exemple, vous voulez commander par ID)

0
codeulike

Il existe une méthode assez simple pour T-SQL, bien que je ne sois pas sûr que ce soit efficace si vous sautez un grand nombre de lignes.

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

Si vous utilisez .Net, vous pouvez utiliser les éléments suivants, par exemple, un IEnumerable avec vos résultats de données:

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

Cela signifie que vous obtenez toutes les données du stockage de données.

0
Tomas Aschan

Pourquoi ne pas faire deux requêtes:

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;
0
Jonathan Mueller