web-dev-qa-db-ja.com

ページングを実装する効率的な方法

ページングにLINQのSkip()およびTake()メソッドを使用するか、SQLクエリを使用して独自のページングを実装する必要がありますか?

どちらが最も効率的ですか?なぜ私は一方をもう一方よりも選ぶのでしょうか?

SQL Server 2008、ASP.NET MVC、およびLINQを使用しています。

114
StoneHeart

疑問に対する簡単な答えを提供しようとすると、(データベースサーバーとしてSQL 2005/2008を使用して)linqでskip(n).take(m)メソッドを実行すると、クエリはSelect ROW_NUMBER() Over ...ステートメントを使用します。 withは、なんらかの形でSQLエンジンの直接ページングです。

例を挙げると、mtcityというdbテーブルがあり、次のクエリを作成しました(linq to entitiesでも動作します)。

using (DataClasses1DataContext c = new DataClasses1DataContext())
{
    var query = (from MtCity2 c1 in c.MtCity2s
                select c1).Skip(3).Take(3);
    //Doing something with the query.
}

結果のクエリは次のようになります。

SELECT [t1].[CodCity], 
    [t1].[CodCountry], 
    [t1].[CodRegion], 
    [t1].[Name],  
    [t1].[Code]
FROM (
    SELECT ROW_NUMBER() OVER (
        ORDER BY [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]) AS [ROW_NUMBER], 
        [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
    FROM [dbo].[MtCity] AS [t0]
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]

これはウィンドウ化されたデータアクセスです(非常にクールで、最初からデータが返され、条件が満たされている限りテーブルにアクセスします)。これは次のようになります。

With CityEntities As 
(
    Select ROW_NUMBER() Over (Order By CodCity) As Row,
        CodCity //here is only accessed by the Index as CodCity is the primary
    From dbo.mtcity
)
Select [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

例外として、この2番目のクエリは、データアクセスウィンドウを作成するためにインデックスのみを使用するため、linqの結果よりも速く実行されます。つまり、何らかのフィルタリングが必要な場合、フィルタリングはエンティティリスト(行が作成される場所)に存在する(または存在する)必要があり、良好なパフォーマンスを維持するためにいくつかのインデックスも作成する必要があります。

さて、何が良いですか?

ロジックにかなり堅実なワークフローがある場合、適切なSQL方法の実装は複雑になります。その場合、LINQがソリューションになります。

ロジックのその部分を(ストアドプロシージャで)SQLに直接下げることができる場合は、(インデックスを使用して)2番目に示したクエリを実装し、SQLの実行プランを生成して格納できるため、さらに改善されます。クエリ(パフォーマンスの向上)。

170
rodrigoelp

使用してみてください

FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY

sQLサーバーで501から600までの行をメモリにロードせずに取得します。この構文は SQL Server 2012 でのみ利用可能になっていることに注意してください

49
d.popov

LINQ-to-SQLはOFFSET句を生成します(他の人が言及しているように、おそらくROW_NUMBER() OVER()を使用してエミュレートされます ) 、SQLでページングを実行するまったく異なる、はるかに速い方法があります。これは、 こちらのブログ投稿 で説明されているように、「シーク方法」と呼ばれます。

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にジャンプできません。しかし、多くの場合、あなたはとにかくそこまでジャンプしたくない。代わりに、インデックス作成に応じて、一定の時間でデータをフェッチできる可能性があるはるかに高速なクエリを取得します。さらに、基礎となるデータが変更されても(たとえば、ページ4にいる間にページ1に)、ページは「安定」のままです。

これは、たとえば、Webアプリケーションでより多くのデータを遅延読み込みするときにページングを実装する最適な方法です。

「シークメソッド」は keyset paging とも呼ばれます。

10
Lukas Eder

LinqToSqlは、.Skip(N1).Take(N2)を自動的にTSQL構文に変換します。実際、Linqで行うすべての「クエリ」は、実際にはバックグラウンドでSQLクエリを作成するだけです。これをテストするには、アプリケーションの実行中にSQLプロファイラーを実行します。

スキップ/テイクの方法論は、私と私が読んだ他の人にとって非常にうまく機能しました。

好奇心から、Linqのスキップ/テイクよりも効率的であると信じている自己ページングクエリの種類は何ですか?

5
mandreko

ストアドプロシージャ内では、動的SQLでラップされたCTEを使用します(アプリケーションではデータサーバー側の動的ソートが必要なため)。必要に応じて、基本的な例を提供できます。

LINQが生成するT/SQLを見る機会はありませんでした。誰かがサンプルを投稿できますか?

追加のセキュリティ層が必要なため、LINQまたはテーブルへの直接アクセスは使用しません(動的SQLがこれを多少中断することを許可しました)。

このような何かがトリックを行う必要があります。パラメーターなどのパラメーター化された値を追加できます。

exec sp_executesql 'WITH MyCTE AS (
    SELECT TOP (10) ROW_NUMBER () OVER ' + @SortingColumn + ' as RowID, Col1, Col2
    FROM MyTable
    WHERE Col4 = ''Something''
)
SELECT *
FROM MyCTE
WHERE RowID BETWEEN 10 and 20'
4
mrdenny

SQL Server 2008の場合:

DECLARE @PAGE INTEGER = 2
DECLARE @TAKE INTEGER = 50

SELECT [t1].*
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[COLUMNORDER] DESC) AS [ROW_NUMBER], [t0].*
    FROM [dbo].[TABLA] AS [t0]
    WHERE ([t0].[COLUMNS_CONDITIONS] = 1)
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN ((@PAGE*@TAKE) - (@TAKE-1)) AND (@PAGE*@TAKE)
ORDER BY [t1].[ROW_NUMBER]

T0にはすべてのレコードt1にはそのページに対応するレコードのみ

2
ch2o

パフォーマンスをさらに向上させることができます、これをチェック

From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

この方法でfromを使用すると、より良い結果が得られます。

From   dbo.MtCity  t0
   Inner Join  CityEntities c on c.CodCity = t0.CodCity

理由:MtCityに参加する前に多くのレコードを削除するCityEntitiesテーブルのwhereクラスを使用しているため、パフォーマンスが何倍も向上することを100%確信しています...

とにかく、rodrigoelpによる答えは本当に役に立ちます。

ありがとう

0
Ali Adravi

2008年には、Skip()。Take()を使用できません

方法は次のとおりです。

var MinPageRank = (PageNumber - 1) * NumInPage + 1
var MaxPageRank = PageNumber * NumInPage

var visit = Visita.FromSql($"SELECT * FROM (SELECT [RANK] = ROW_NUMBER() OVER (ORDER BY Hora DESC),* FROM Visita WHERE ) A WHERE A.[RANK] BETWEEN {MinPageRank} AND {MaxPageRank}").ToList();
0
Belen Martin

PageIndexを渡すことで、この簡単な方法でページングを実装できます。

Declare @PageIndex INT = 1
Declare  @PageSize INT = 20

Select ROW_NUMBER() OVER ( ORDER BY Products.Name ASC )  AS RowNumber,
    Products.ID,
    Products.Name
into #Result 
From Products

SELECT @RecordCount = COUNT(*) FROM #Results 

SELECT * 
FROM #Results
WHERE RowNumber
BETWEEN
    (@PageIndex -1) * @PageSize + 1 
    AND
    (((@PageIndex -1) * @PageSize + 1) + @PageSize) - 1
0
Rae Lee