web-dev-qa-db-fra.com

Requête SQL Server avec pagination et nombre

Je veux faire une requête de base de données avec pagination. J'ai donc utilisé une expression de table commune et une fonction classée pour y parvenir. Regardez l'exemple ci-dessous.

declare @table table (name varchar(30));
insert into @table values ('Jeanna Hackman');
insert into @table values ('Han Fackler');
insert into @table values ('Tiera Wetherbee');
insert into @table values ('Hilario Mccray');
insert into @table values ('Mariela Edinger');
insert into @table values ('Darla Tremble');
insert into @table values ('Mammie Cicero');
insert into @table values ('Raisa Harbour');
insert into @table values ('Nicholas Blass');
insert into @table values ('Heather Hayashi');

declare @pagenumber int = 2;
declare @pagesize int = 3;
declare @total int;

with query as
(
    select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line from @table
)
select top (@pagesize) name from query
    where line > (@pagenumber - 1) * @pagesize

Ici, je peux spécifier les variables @pagesize et @pagenumber pour me donner juste les enregistrements que je veux. Cependant, cet exemple (qui provient d'une procédure stockée) est utilisé pour créer une pagination de grille dans une application Web. Cette application Web nécessite d'afficher les numéros de page. Par exemple, si un a 12 enregistrements dans la base de données et la taille de la page est de 3, alors je devrai montrer 4 liens, chacun représentant une page.

Mais je ne peux pas faire cela sans savoir combien d'enregistrements sont là, et cet exemple me donne juste le sous-ensemble d'enregistrements.

Ensuite, j'ai changé la procédure stockée pour retourner le nombre (*).

declare @pagenumber int = 2;
declare @pagesize int = 3;
declare @total int;
with query as
(
    select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line, total = count(*) over()from @table
)
select top (@pagesize) name, total from query
    where line > (@pagenumber - 1) * @pagesize

Ainsi, avec chaque ligne, il affichera le nombre total d'enregistrements. Mais je n'aimais pas ça.

Ma question est de savoir s'il existe un meilleur moyen (performance) de le faire, peut-être en définissant la variable @total sans renvoyer ces informations dans SELECT. Ou cette colonne totale est-elle quelque chose qui ne nuira pas trop aux performances?

Merci

26
Fabio

En supposant que vous utilisez MSSQL 2012, vous pouvez utiliser Offset and Fetch qui nettoie considérablement la pagination côté serveur. Nous avons constaté que les performances sont bonnes et, dans la plupart des cas, meilleures. En ce qui concerne le nombre total de colonnes, utilisez simplement la fonction de fenêtre ci-dessous en ligne ... elle n'inclura pas les limites imposées par 'offset' et 'fetch'.

Pour Row_Number, vous pouvez utiliser les fonctions de la fenêtre comme vous l'avez fait, mais je vous recommande de calculer ce côté client comme (pagenumber * pagesize + resultsetRowNumber), donc si vous êtes sur la 5e page de 10 résultats et sur la troisième ligne vous afficherait la ligne 53.

Appliqué à une table Commandes avec environ 2 millions de commandes, j'ai trouvé ce qui suit:

VERSION RAPIDE

Cela a duré moins d'une seconde. La bonne chose à ce sujet est que vous pouvez effectuer votre filtrage dans l'expression de table commune une fois et cela s'applique à la fois au processus de pagination et au nombre. Lorsque vous avez de nombreux prédicats dans la clause where, cela simplifie les choses.

declare @skipRows int = 25,
        @takeRows int = 100,
        @count int = 0

;WITH Orders_cte AS (
    SELECT OrderID
    FROM dbo.Orders
)

SELECT 
    OrderID,
    tCountOrders.CountOrders AS TotalRows
FROM Orders_cte
    CROSS JOIN (SELECT Count(*) AS CountOrders FROM Orders_cte) AS tCountOrders
ORDER BY OrderID
OFFSET @skipRows ROWS
FETCH NEXT @takeRows ROWS ONLY;

VERSION LENTE

Cela a pris environ 10 secondes, et c'est le comte (*) qui a causé la lenteur. Je suis surpris que ce soit si lent, mais je soupçonne qu'il s'agit simplement de calculer le total pour chaque ligne. C'est très propre cependant.

declare @skipRows int = 25,
@takeRows int = 100,
@count int = 0


SELECT 
    OrderID,
    Count(*) Over() AS TotalRows
FROM Location.Orders
ORDER BY OrderID
OFFSET @skipRows ROWS
FETCH NEXT @takeRows ROWS ONLY;

CONCLUSION

Nous avons déjà suivi ce processus d'optimisation des performances et nous avons constaté qu'il dépendait de la requête, des prédicats utilisés et des index impliqués. Par exemple, la seconde fois que nous avons introduit une vue, elle a été supprimée, nous interrogeons donc la table de base, puis joignons la vue (qui inclut la table de base) et elle fonctionne très bien.

Je suggérerais d'avoir quelques stratégies simples et de les appliquer à des requêtes à forte valeur ajoutée.

49
BlackjacketMack
DECLARE @pageNumber INT = 1  , 
        @RowsPerPage INT = 20

SELECT  *
FROM    TableName
ORDER BY Id
        OFFSET ( ( @pageNumber - 1 ) * @RowsPerPage ) ROWS
             FETCH NEXT @RowsPerPage ROWS ONLY;
4
Morteza Sefidi
@pagenumber=5
@pagesize=5

Créez une expression de table commune et écrivez une logique comme celle-ci

Between ((@pagenumber-1)*(@pagesize))+1 and (@pagenumber *@pagesize)
1
Sunil

Et si vous calculez le nombre à l'avance?

declare @pagenumber int = 2;
declare @pagesize int = 3;
declare @total int;

SELECT @total = count(*)
FROM @table

with query as
(
   select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line from @table
)
select top (@pagesize) name, @total total from query
where line > (@pagenumber - 1) * @pagesize

Une autre façon consiste à calculer max(line). Vérifiez le lien

Renvoyer le nombre total d'enregistrements de SQL Server lors de l'utilisation de ROW_NUMBER

UPD:

Pour une requête unique, vérifiez la réponse de marc_s sur le lien ci-dessus.

    with query as
    (
       select name, ROW_NUMBER() OVER(ORDER BY name ASC) as line from @table
    )
    select top (@pagesize) name, 
       (SELECT MAX(line) FROM query) AS total 
    from query
    where line > (@pagenumber - 1) * @pagesize
1
hgulyan

Il existe de nombreuses façons de parvenir à la pagination: j'espère que ces informations vous seront utiles, ainsi qu'à d'autres.

Exemple 1: utilisation de la clause suivante offset-fetch. introduire en 2005

declare @table table (name varchar(30));
insert into @table values ('Jeanna Hackman');
insert into @table values ('Han Fackler');
insert into @table values ('Tiera Wetherbee');
insert into @table values ('Hilario Mccray');
insert into @table values ('Mariela Edinger');
insert into @table values ('Darla Tremble');
insert into @table values ('Mammie Cicero');
insert into @table values ('Raisa Harbour');
insert into @table values ('Nicholas Blass');
insert into @table values ('Heather Hayashi');

declare @pagenumber int = 1
declare @pagesize int = 3

--this is a CTE( common table expression and this is introduce in 2005)
with query as
(
  select ROW_NUMBER() OVER(ORDER BY name ASC) as line, name from @table
) 

--order by clause is required to use offset-fetch
select * from query
order by name 
offset ((@pagenumber - 1) * @pagesize) rows
fetch next @pagesize rows only

Exemple 2: en utilisant la fonction row_number () et entre

declare @table table (name varchar(30));
insert into @table values ('Jeanna Hackman');
insert into @table values ('Han Fackler');
insert into @table values ('Tiera Wetherbee');
insert into @table values ('Hilario Mccray');
insert into @table values ('Mariela Edinger');
insert into @table values ('Darla Tremble');
insert into @table values ('Mammie Cicero');
insert into @table values ('Raisa Harbour');
insert into @table values ('Nicholas Blass');
insert into @table values ('Heather Hayashi');

declare @pagenumber int = 2
declare @pagesize int = 3

SELECT *
FROM 
(select ROW_NUMBER() OVER (ORDER BY PRODUCTNAME) AS RowNum, * from Products)
as Prodcut
where RowNum between (((@pagenumber - 1) * @pageSize )+ 1) 
and (@pagenumber * @pageSize )

J'espère que cela sera utile à tous

0
Devendra Gohel