日付範囲を使用して内部結合を行うクエリの最適化に問題があります。クエリの目的は、毎日のデータを取得し、週ごとに要約することです。
Select pcw.EndDate WeekEndDate, h.Store, SUM(h.DeliveryChargesTotal) DeliveryChargesTotal
from Daily_GC_Headers h
inner join PeriodCalendar_Weeks pcw
on h.SalesDate between pcw.StartDate and pcw.EndDate
where SalesDate between @StartDate and @EndDate and isCanceled = 0
group by pcw.EndDate, h.Store
Daily_GC_Headers
テーブルの簡略化されたスキーマ(1380万行、WHERE句の約540万一致基準):
Store - Varchar(10) (PK)
SalesDate - Date (PK)
TicketNumber - SmallInt (PK; starts over 1 each day at each store.)
IsCanceled - Bit
DeliveryChargesTotal - Decimal(9,2)
PeriodCalendar_Weeks
テーブルの簡略化されたスキーマ(570行、53が基準に一致):
Year - smallint (PK)
Period - tinyint (PK)
Week - tinyint (PK)
StartDate - Date
EndDate - Date
このクエリは、SSMSで約15秒かかります。 Daily_GC_Headers
を単独で(およびStore
でグループ化するだけで)クエリすると、2秒かかります。 PeriodCalendar_Weeks
に対するクエリは「インスタント」です。
DBCC SHOW_STATISTICS
は、統計が両方とも最新であることを示します(更新するために毎週ジョブを実行します)。プランのキャッシュをクリアしてみました。
実行計画がおかしい。たとえば、PeriodCalendar_Weeks
でEager Spoolを実行しています。推定行は156.6ですが、実際の行は153,971です。次に、その最初のスプールの結果をフィルタリングし、レイジースプールを実行します。その2番目のスプールの推定/実際の行は5.4ミリオンです。ただし、基になるテーブルに600行未満の行があります
これを最適化するために何を探したり、何をすべきですか?
追加情報
明確にするために、私は最初、週の表で過度に単純化したPKについて説明しました。上記のスキーマを更新して、完全なキーを表示します。ヘッダーに記載されているPKは、完全なキーです(かつていました)。
Weeksテーブルの一部の行のスクリーンショット:
週テーブルの統計:
ヘッダーテーブルの一部の統計。表の履歴全体(3年)について、約5〜10日ごとのヒストグラムレコードがあるようです。
これを行うには、前に戻って期間テーブルに結合する必要がないため、はるかに効率的です。
DECLARE @StartDate DATE, @EndDate DATE;
Select @StartDate = Min(StartDate), @EndDate = MAX(EndDate)
from dbo.PeriodCalendar_Weeks pcw
where (pcw.Year = @Year and pcw.Period < @Period)
or (pcw.Year = @Year and pcw.Period = @Period and pcw.Week <= @Week)
or (pcw.Year = @Year -1 and pcw.Period >= @Period);
SELECT
WeekEndDate = DATEADD(DAY, 6, DATEADD(WEEK, SalesWeek, @StartDate)),
Store,
DeliveryChargesTotal = dct
FROM
(
SELECT DATEDIFF(DAY, @StartDate, SalesDate)/7, Store, SUM(DeliveryChargesTotal)
FROM dbo.Daily_GC_Headers
WHERE SalesDate BETWEEN @StartDate AND @EndDate AND isCanceled = 0
GROUP BY DATEDIFF(DAY, @StartDate, SalesDate)/7, Store
) AS x (SalesWeek, Store, dct)
ORDER BY WeekEndDate, Store;
フィルターされたインデックスは、isCanceled = 1
(Store
のカーディナリティに応じて、これらは考えられる提案であり、最適ではない場合があります):
CREATE INDEX x ON dbo.Daily_GC_Headers
(SalesDate) INCLUDE (Store, DeliveryChargesTotal)
WHERE isCanceled = 0;
行が非常に少ない場合、isCanceled = 1
、これは良いかもしれません:
CREATE INDEX x ON dbo.Daily_GC_Headers
(SalesDate, IsCanceled) INCLUDE (Store, DeliveryChargesTotal);
どちらもテストシステムで試してみる価値があり、どちらの場合もStore
をキーに移動するか、後者の場合はIsCanceled
をINCLUDE
リストに移動します。私のシステムでは、INCLUDE
リストの日付を除くすべてで最良の結果が見つかりました。
CREATE INDEX x ON dbo.Daily_GC_Headers
(SalesDate) INCLUDE (Store, IsCanceled, DeliveryChargesTotal);
繰り返しますが、これらのいずれかが機能するかどうか、または上記のクエリがSQL Serverから直接異なる/より良い推奨を与えるかどうかをテストする必要があります。