web-dev-qa-db-ja.com

SQL Server、複数のテーブルにUNION ALLを使用してから、ページングの実装

複数のテーブルのページングと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の間隔が長くなると、結果セット全体から数行のみを取得しようとしているときに、クエリが大幅に遅くなります。

同様のロジックでシンプルだがより良いアプローチを実装する方法を教えてください。 :(

12

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))時間がかかる理由についても説明します。
このクエリを実行するには、データベースは次のことを行う必要があります。

  1. 複数のリストを1つのリストに結合するには-各テーブルにO(n)時間がかかります。ここで、n-テーブル内の行の総数。
  2. リストをcol1でソート-O(n * log(n))を取ります。ここで、n-は行の総数です。
  3. ソートされた順序でリストをトラバースし、@ startRow行をスキップして、次の@PageCount行を取得します。

このクエリのパフォーマンスがまだ低く、増加したい場合は、次のことを試してください。

  • すべてのテーブルでcol1に基づいてクラストインデックスを作成します
  • すべてのテーブルでcol1に基づいて非クラスターインデックスを作成し、**選択リストに出力する他のすべての列を含めます**。
2

同様のテーブルが5つあるため、データベースの設計に問題がある可能性があります。ただし、これに加えて、UNION ALLクエリを永続テーブルまたは適切なインデックスを持つ一時#テーブルにマテリアライズし、最後にROW_NUMBER()句を使用してマテリアライズされたデータセットをページングすることができます。

1
YuGagarin

従来の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)が各テーブルでインデックス付けされていることを確認する必要があります。

0
Lukas Eder

テーブルはページングの結果セットで順序付けられているため(Union ALLはソートされません)、5つのテーブルすべてから選択する理由はありません。コードを次のように変更する必要があります:

  • 表1からクエリを実行します。十分なレコードがあるかどうかを確認します。
  • そうでない場合は、表2からクエリを実行します。

毎回クエリされるレコードの数に応じてオフセットカウントを管理します。このように、必要なテーブルのみをクエリします。

フィルタに従ってテーブル内のレコード数を選択して、そこからデータをクエリする必要があるかどうかを知ることで、最適化することもできます。したがって、レコード30〜50が必要で、table1に一致するレコードが20個しかない場合は、完全にスキップできます。

0
Amir Pelled
_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)のインデックスを同時に作成することはできません。したがって、両方のインデックス作成戦略を試して、最も効果的なものを選択する必要があります。

0
usr