web-dev-qa-db-ja.com

ページネーションとカウントを使用したSQL Serverクエリ

ページネーションを使用してデータベースクエリを作成します。そこで、共通テーブル式とランク付けされた関数を使用してこれを実現しました。以下の例を見てください。

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

ここで、@ pagesize変数と@pagenumber変数を指定して、必要なレコードだけを指定できます。ただし、この例(ストアドプロシージャから取得)は、Webアプリケーションでグリッドのページ分割を行うために使用されます。このWebアプリケーションでは、ページ番号を表示する必要があります。たとえば、データベースに12個のレコードがあり、ページサイズが3である場合、4つのリンクを表示する必要があります。各リンクはページを表します。

しかし、レコードがいくつあるかを知らずにこれを行うことはできません。この例では、レコードのサブセットのみを提供しています。

次に、ストアドプロシージャを変更してcount(*)を返します。

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

そのため、各行とともに、レコードの総数が表示されます。しかし、私はそれが好きではありませんでした。

私の質問は、これを行うためのより良い方法(パフォーマンス)があるかどうかです。おそらく、この情報をSELECTで返さずに@total変数を設定します。または、この合計列は、パフォーマンスにあまり害を与えないものですか?

ありがとう

26
Fabio

MSSQL 2012を使用していると仮定すると、Offset and Fetchサーバー側のページングを大幅にクリーンアップします。パフォーマンスは良好であり、ほとんどの場合より優れていることがわかりました。合計列数を取得する限り、インラインの下のウィンドウ関数を使用するだけです...「オフセット」と「フェッチ」によって課される制限は含まれません。

Row_Numberの場合は、ウィンドウ関数をそのまま使用できますが、そのクライアント側を(pagenumber * pagesize + resultsetRowNumber)として計算することをお勧めします。行53を出力します。

約200万件の注文があるOrdersテーブルに適用すると、次のことがわかりました。

高速バージョン

これは1秒未満で実行されました。良い点は、共通テーブル式で一度フィルタリングを行うことができ、ページングプロセスとカウントの両方に適用されることです。 where句に多くの述語がある場合、これにより物事が簡単になります。

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;

スローバージョン

これには約10秒かかり、Count(*)が遅延を引き起こしました。これが非常に遅いことに驚いていますが、単に各行の合計を計算しているだけだと思います。でもとてもきれいです。

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;

[〜#〜]結論[〜#〜]

以前にこのパフォーマンスチューニングプロセスを実行しましたが、実際にはクエリ、使用される述語、および関連するインデックスに依存することがわかりました。たとえば、2番目に、それがチャグしたビューを導入したため、実際にベーステーブルからクエリを実行し、ビュー(ベーステーブルを含む)を結合すると、実際に非常にうまく機能します。

いくつかの簡単な戦略を用意し、それらを一気に高価値のクエリに適用することをお勧めします。

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

共通テーブル式を作成し、次のようなロジックを記述します

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

事前にカウントを計算するとどうなりますか?

_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
_

別の方法は、max(line)を計算することです。リンクを確認してください

ROW_NUMBERの使用時にSQL Serverから合計レコードを返す

UPD:

単一のクエリについては、上記のリンクでmarc_sの回答を確認してください。

_    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

ページネーションを実現する方法はたくさんあります。この情報があなたや他の人に役立つことを願っています。

例1:オフセットフェッチのnext句を使用します。 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

例2:row_number()関数とその間の使用

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 )

これらがすべての人に役立つことを願っています

0
Devendra Gohel