SQL から結果のページを返すにはどうすればよいですか?
-
08-06-2019 - |
質問
多くのアプリケーションには、データベース テーブルのデータを一度に 1 ページずつ表示するグリッドがあります。それらの多くでは、ユーザーがページごとのレコード数を選択したり、任意の列で並べ替えたり、結果を前後に移動したりすることもできます。
テーブル全体をクライアントに取り込み、クライアントでデータをフィルタリングすることなく、このパターンを実装するための適切なアルゴリズムは何ですか。表示したいレコードだけをユーザーに提供するにはどうすればよいでしょうか?
LINQ はソリューションを簡素化しますか?
解決
MS SQL Server 2005 以降では、 ROW_NUMBER() 動作するようです:
DECLARE @PageNum AS INT;
DECLARE @PageSize AS INT;
SET @PageNum = 2;
SET @PageSize = 10;
WITH OrdersRN AS
(
SELECT ROW_NUMBER() OVER(ORDER BY OrderDate, OrderID) AS RowNum
,OrderID
,OrderDate
,CustomerID
,EmployeeID
FROM dbo.Orders
)
SELECT *
FROM OrdersRN
WHERE RowNum BETWEEN (@PageNum - 1) * @PageSize + 1
AND @PageNum * @PageSize
ORDER BY OrderDate
,OrderID;
他のヒント
LINQ を使用するか、LINQ の機能をコピーしてみることをお勧めします。LINQ Take メソッドと Skip メソッドを使用してページ データを取得するアプリがあります。コードは次のようになります。
MyDataContext db = new MyDataContext();
var results = db.Products
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
SQL Server Profiler を実行すると、LINQ がこのクエリを次のような SQL に変換していることがわかります。
SELECT [ProductId], [Name], [Cost], and so on...
FROM (
SELECT [ProductId], [Name], [Cost], [ROW_NUMBER]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [Name]) AS [ROW_NUMBER],
[ProductId], [Name], [Cost]
FROM [Products]
)
WHERE [ROW_NUMBER] BETWEEN 10 AND 20
)
ORDER BY [ROW_NUMBER]
平易な英語で:
1.行をフィルターし、ROW_NUMBER 関数を使用して希望の順序で行番号を追加します。
2.フィルター (1) を使用して、ページに必要な行番号のみを返します。
3.(2) 行番号で並べ替えます。これは希望する順序 (この場合は名前順) と同じです。
データベースでページネーションを行うには基本的に 2 つの方法があります (SQL Server を使用していると仮定します)。
オフセットの使用
他の人は、どのようにして ROW_NUMBER() OVER()
ランキング機能を利用してページを実行することができます。SQL Server 2012 に最終的に SQL 標準のサポートが含まれたことは言及する価値があります。 OFFSET .. FETCH
句:
SELECT first_name, last_name, score
FROM players
ORDER BY score DESC
OFFSET 40 ROWS FETCH NEXT 10 ROWS ONLY
SQL Server 2012 を使用していて、下位互換性が問題にならない場合は、まれなケースで SQL Server によってより最適に実行されるため、おそらくこの句を優先する必要があります。
SEEKメソッドの使用
SQL でページングを実行する、まったく異なる、はるかに高速な、しかしあまり知られていない方法があります。これは、「seek メソッド」と呼ばれることがよくあります。 このブログ投稿はこちら.
SELECT TOP 10 first_name, last_name, score
FROM players
WHERE (score < @previousScore)
OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC
の @previousScore
そして @previousPlayerId
値は、前のページの最後のレコードのそれぞれの値です。これにより、「次の」ページを取得できるようになります。もし ORDER BY
方向は ASC
, 、単に使用してください >
その代わり。
上記の方法では、最初に前の 40 レコードをフェッチしない限り、すぐにページ 4 にジャンプすることはできません。しかし、多くの場合、とにかくそこまでジャンプしたくないのです。代わりに、インデックス作成に応じて、一定時間でデータをフェッチできる可能性のある、はるかに高速なクエリが得られます。さらに、基礎となるデータが変化しても、ページは「安定」したままになります (例:1 ページ目では、あなたは 4 ページ目です)。
これは、たとえば Web アプリケーションでより多くのデータを遅延読み込みする場合にページングを実装する最良の方法です。
「seek メソッド」とも呼ばれることに注意してください。 キーセットのページング.
.Net 3.5 のラムダ式および匿名クラスと組み合わせた LINQ とても この種のことを簡素化します。
データベースのクエリ:
var customers = from c in db.customers
join p in db.purchases on c.CustomerID equals p.CustomerID
where p.purchases > 5
select c;
ページあたりのレコード数:
customers = customers.Skip(pageNum * pageSize).Take(pageSize);
任意の列による並べ替え:
customers = customers.OrderBy(c => c.LastName);
選択されたフィールドのみをサーバーから取得します。
var customers = from c in db.customers
join p in db.purchases on c.CustomerID equals p.CustomerID
where p.purchases > 5
select new
{
CustomerID = c.CustomerID,
FirstName = c.FirstName,
LastName = c.LastName
};
これにより、そのプロパティにアクセスできる静的に型指定された匿名クラスが作成されます。
var firstCustomer = customer.First();
int id = firstCustomer.CustomerID;
クエリの結果はデフォルトで遅延読み込みされるため、実際にデータが必要になるまでデータベースと通信することはありません。また、.Net の LINQ では、行った変更のデータコンテキストを保持し、変更したフィールドのみを更新することにより、更新が大幅に簡素化されます。
オラクルのソリューション:
select * from (
select a.*, rownum rnum from (
YOUR_QUERY_GOES_HERE -- including the order by
) a
where rownum <= MAX_ROW
) where rnum >= MIN_ROW
MS SQL 2005 で使用するソリューションがいくつかあります。
そのうちの 1 つは ROW_NUMBER() です。しかし、個人的には、ROW_NUMBER() はあまり好きではありません。大きな結果が得られないからです (私が取り組んでいる DB は非常に大きく、1 TB を超えるデータで秒間に数千のクエリが実行されます。ご存知の通り、大規模なソーシャル ネットワーキングです)サイト)。
これが私のお気に入りの解決策です。
T-SQLの擬似コードのようなものを使用します。
名、姓でソートされたユーザーの 2 ページ目を見つけてみましょう。各ページには 10 件のレコードがあります。
@page = 2 -- input parameter
@size = 10 -- can be optional input parameter
if @page < 1 then begin
@page = 1 -- check page number
end
@start = (@page-1) * @size + 1 -- @page starts at record no @start
-- find the beginning of page @page
SELECT TOP (@start)
@forename = forename,
@surname = surname
@id = id
FROM
users
ORDER BY
forename,
surname,
id -- to keep correct order in case of have two John Smith.
-- select @size records starting from @start
SELECT TOP (@size)
id,
forename,
surname
FROM
users
WHERE
(forename = @forename and surname = @surname and id >= @id) -- the same name and surname, but bigger id
OR (forename = @forename and surname > @surname) -- the same name, but bigger surname, id doesn't matter
OR (forename > @forename) -- bigger forename, the rest doesn't matter
ORDER BY
forename,
surname,
id
実際、LINQ には Skip メソッドと Take メソッドがあり、これらを組み合わせてフェッチするレコードを選択できます。
そちらをチェックしてみてください。
DBの場合: SQL Server 2005 のページネーション
これについては議論があります ここ
この手法では、150,000 行のデータベースから 78 ミリ秒でページ番号 100,000 を取得します。
オプティマイザーの知識と SET ROWCOUNT を使用して、要求されたページ内の最初の EmployeeID が開始点のローカル変数に保存されます。次に、SET ROWCOUNT を @maximumRows で要求される最大レコード数に設定します。これにより、より効率的な方法で結果セットをページングできるようになります。この方法を使用すると、ローカルに作成されたテーブルではなくベース テーブルに直接アクセスされるため、テーブル上の既存のインデックスも利用できます。
残念ながら、それが現在受け入れられている答えよりも優れているかどうかを判断することはできません。