複数のテーブルのページングとUNION ALL
の使用についてもサポートが必要です。
UNION ALL
を使用して複数のテーブルを結合し、特定の行数のみを返す場合、最適化されたページングを実装するにはどうすればよいですか。
declare @startRow int
declare @PageCount int
set @startRow = 0
set @PageCount = 20
set rowcount @PageCount
select Row_Number() OVER(Order by col1) as RowNumber, col1, col2
from
(
select col1, col2 from table1 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table2 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table3 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table4 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table5 where datetimeCol between (@dateFrom and @dateTo)
) as tmpTable
where RowNumber > @startRow
表3、4、および5には膨大な数の行(数百万行)があり、表1および2には数千行しかない場合があります。
StartRowが「0」の場合、行1から20までのデータのみが必要です(表1から)。正しい結果が得られていますが、SQLサーバーがすべてのデータを試行してフィルタリングしようとしている間、残りのテーブルに高いオーバーヘッドがあります。
@dateFromと@dateToの間隔が長くなると、結果セット全体から数行のみを取得しようとしているときに、クエリが大幅に遅くなります。
同様のロジックでシンプルだがより良いアプローチを実装する方法を教えてください。 :(
OFFSET FETCH句の使用を検討してください(MSSQL 2012以降で機能します)。
declare @startRow int
declare @PageCount int
set @startRow = 0
set @PageCount = 20
select col1, col2
from
(
select col1, col2 from table1 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table2 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table3 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table4 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table5 where datetimeCol between (@dateFrom and @dateTo)
) as tmpTable
order by col1
offset @startRow rows
fetch next @PageCount rows only
また、このクエリに常にO(n * log(n))時間がかかる理由についても説明します。
このクエリを実行するには、データベースは次のことを行う必要があります。
このクエリのパフォーマンスがまだ低く、増加したい場合は、次のことを試してください。
同様のテーブルが5つあるため、データベースの設計に問題がある可能性があります。ただし、これに加えて、UNION ALLクエリを永続テーブルまたは適切なインデックスを持つ一時#テーブルにマテリアライズし、最後にROW_NUMBER()句を使用してマテリアライズされたデータセットをページングすることができます。
従来のOFFSET
ベースのページングを適用する代わりに( SQL Server 2012がネイティブでサポートするようになりました )、特定のユースケースは、「シーク」と呼ばれる方法から大きな利益を得ることができると思います。 このブログ投稿はこちら で説明されているように。クエリは次のようになります。
select top 20 col1, col2
from
(
select col1, col2 from t1 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from t2 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from t3 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from t4 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from t5 where datetimeCol between (@dateFrom and @dateTo)
) as tmpTable
where (col1 > @lastValueForCol1)
or (col1 = @lastValueForCol1 and col2 > @lastValueForCol2)
order by col1, col2
@lastValueForCol1
と@lastValueForCol2
の値は、前のページの最後のレコードのそれぞれの値です。これにより、「次の」ページを取得できます。 ORDER BY
方向がDESC
の場合は、代わりに<
を使用してください。 (col1, col2)
がtmpTable
全体でグローバルに一意でない場合は、クエリとWHERE
およびORDER BY
句に別の列を追加して、その間のレコードが失われないようにする必要があります。ページ。
上記の方法では、最初に前の40レコードをフェッチしなければ、すぐに3ページにジャンプすることはできません。しかし、多くの場合、とにかくそこまでジャンプしたくないでしょう。代わりに、インデックス作成によっては、一定時間でデータをフェッチできる可能性のある、はるかに高速なクエリを取得できます。さらに、基になるデータが変更されても、ページは「安定」したままです(たとえば、1ページ目、4ページ目)。
「seekメソッド」は キーセットページング とも呼ばれることに注意してください。
「seekメソッド」を使用したページングは、OFFSET
を使用した場合よりも常に高速ですが、(col1, col2)
が各テーブルでインデックス付けされていることを確認する必要があります。
テーブルはページングの結果セットで順序付けられているため(Union ALLはソートされません)、5つのテーブルすべてから選択する理由はありません。コードを次のように変更する必要があります:
毎回クエリされるレコードの数に応じてオフセットカウントを管理します。このように、必要なテーブルのみをクエリします。
フィルタに従ってテーブル内のレコード数を選択して、そこからデータをクエリする必要があるかどうかを知ることで、最適化することもできます。したがって、レコード30〜50が必要で、table1に一致するレコードが20個しかない場合は、完全にスキップできます。
_select col1, col2 from table1 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table2 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table3 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table4 where datetimeCol between (@dateFrom and @dateTo)
union all
select col1, col2 from table5 where datetimeCol between (@dateFrom and @dateTo)
_
ページングに使用するソートキーにインデックスがある場合、基本的に通常のテーブルと同じくらい効率的です。これにより、通常、すべてのテーブルがマージ連結されたクエリプランが作成されます。マージ連結はストリーミング操作です。コストは、テーブルの行数ではなく、描画された行数に比例します。
SQL Serverの行番号によるページングは、目的のウィンドウに到達するまで行の開始から終了までを列挙することで常に機能します。行がテーブルから描画されるか、複数のマージされたテーブルから描画されるかは、基本的な違いにはなりません。
したがって、これを高速化する良いチャンスは、_col1
_をキーとするカバーインデックスを作成することです。残念ながら、between (@dateFrom and @dateTo)
のインデックスを同時に作成することはできません。したがって、両方のインデックス作成戦略を試して、最も効果的なものを選択する必要があります。