私のクエリは約590行と8列を返します。私が抱えている問題は、クエリが最初から最後まで2分30秒で完了することです。ここにいるすばらしい人々のグループは、より効率的なクエリの書き方についてたくさん教えてくれたので、ここにもう1つあります!
私の変数には日付のみが含まれているため、date
変数を使用し、datetime
は使用していません。また、提案されているように、日付をyyyymmdd
形式で保存しています。 Aaron Bertrand-Bad Habits To Kick による。
このクエリを最適化して結果をより速く返すためにできることはありますか?
DECLARE @Startdate date = '20170101',
@Enddate date = '20170131';
WITH fc As
(
Select
Teacher
,Team
,fc
FROM [Helper].[dbo].[fc]
)
,ia As
(
Select
Teacher
,tia
FROM dbo.ia
WHERE [hiredate] >= @Startdate
AND [hiredate] < DATEADD(DAY,1,@Enddate)
)
,inb As
(
Select
Teacher
,tinb
FROM inb
),
ripcord As
(
Select
Teacher
,pit
FROM [homebase].[dbo].[rip]
)
,YRTR As
(
Select
Teacher
,totamt
FROM totamt
WHERE CAST([begindate] As DATE) BETWEEN CAST(DateAdd(yy, -1, @startdate) As Date)
AND CAST(DateAdd(yy, -1, @enddate) As Date)
)
Select
DISTINCT rost.[Teacher] As Teacher
,[Team Name] = fc.team
,[TIA] = ROUND(SUM(ISNULL(ia.tia,0)),0)
,[SUM] = CAST(ROUND(SUM(ISNULL(inb.tinb,0))+SUM(ISNULL(rip.pit,0)),0) As INT)
,[YR] = ROUND(SUM(ISNULL(tr.totamt,0)),0)
,[fc] = ISNULL(fc,0)
,[1st Count] = COALESCE(b.[students],0)
,[2nd Count] = COALESCE(c.[potstudents],0)
FROM dbo.roster rost
LEFT JOIN fc fc
ON rost.Teacher = fc.Teacher
LEFT JOIN ia ia
ON ia.Teacher = rost.Teacher
LEFT JOIN inb inb
ON rost.Teacher = inb.Teacher
LEFT JOIN ripcord rip
ON rost.Teacher = rip.Teacher
LEFT JOIN YRTR tr
ON rost.Teacher = tr.Teacher
OUTER APPLY
( SELECT
DISTINCT Teacher [Teacher]
, COUNT(students) AS students
FROM students
WHERE Teacher = rost.Teacher
AND regdate >= @Startdate
AND regdate < DATEADD(DAY,1,@Enddate)
GROUP BY Teacher
) AS b
OUTER APPLY
(
Select
DISTINCT Teacher Teacher
,COUNT(potstudents) As potstudents
FROM dbo.potstudents
WHERE Teacher = rost.Teacher
AND returndate >= @Startdate
AND returndate < DATEADD(DAY,1,@Enddate)
GROUP BY Teacher
) AS c
GROUP BY rost.[Teacher],fc.TEAM, fc.fc,b.[students],c.[potstudents]
ORDER BY rost.[Teacher] ASC
[〜#〜]編集[〜#〜]
(リンクを忘れてしまい、申し訳ありません)@sabin bioのリクエストで-これは Query Execution Plan へのリンクです
また、インデックスを持たないCTEのクエリビュー。
まず、クエリオプティマイザーがクエリによって生成したプランを理解します。あなたの質問では、クエリは約590行を返しますが、含まれているクエリプランは18行しか返しません。正しいプランを添付しましたか?とにかく歩きます。右から左に読む:
roster
のテーブル全体をスキャンして、18行を取得します。この結果セットは、次のすべてのネストされたループ結合の外側部分として使用されます。外部結果セットの各行について:
ia
テーブルをフルスキャンしますinb
テーブルをフルスキャンしますripcord
テーブルをフルスキャンしますYRTR
テーブルをフルスキャンしますstudents
テーブルをフルスキャンしますpotstudents
テーブルをフルスキャンしますある時点で、行数は最大19に増加します。_GROUP BY
_は、ノードIDが4のSORT
として実装されます。DISTINCT
は、Sort (Distinct Sort)
、ノードIDは0。
プログラマはクエリプランを直接制御することはできませんが、それに影響を与えるためにできることはたくさんあります。クエリの実行時間が遅すぎるとのことですが、上記の計画はあなたにとって効率的に聞こえますか?クエリオプティマイザのアクションを選択できるとしたらどうでしょうか。 18行を返すためだけに約140のテーブルスキャンを実行しています。非常に少数の行の場合、これは問題ないかもしれませんが、本番環境でより多くのデータがあるようです。
クエリを最適化する1つの方法は、そのクエリのIO要件を減らすことです。ここでは、実装が簡単になるように多くのテーブルスキャンを実行しています。クエリオプティマイザが取得できるようにインデックスを作成します。関連データをより効率的に使用します。ビューを参照している場合でも、ビューで使用されるテーブルにインデックスを作成してパフォーマンスを向上させることができます。例を示すために、fc
に対するテーブルアクセスの述語を次に示します。
_[Test].[dbo].[roster].[Teacher] as [rost].[Teacher]=[Test].[dbo].[fc].[Teacher]
_
Teacher
のfc
列にインデックスを作成すると、そのテーブルに対して異なる、場合によってはより効率的なテーブルアクセスメソッドが作成される可能性があります。オプションで、Teacherテーブルで使用されるすべての列をINCLUDE
列として追加できます。
コードだけを見ると、クエリでローカル変数を使用することの影響に注意する必要があります。クエリオプティマイザーは、クエリプランの作成時にこれらのローカル変数の値を認識しません。ハードコードされたルールに基づいて、デフォルトのカーディナリティの推定を行います。一部のクエリでは、OPTION (RECOMPILE)
ヒントを追加するか、ローカル変数をハードコードされた値に置き換えると、クエリオプティマイザーがプランの作成前に変数の値を知っているため、クエリプランが大幅に改善されます。
また、DISTINCT
と_GROUP BY
_を無計画にクエリに追加しないでください。クエリオプティマイザーがそれらの一部を最適化したように見えますが、ほとんどの場合、同じクエリに_GROUP BY
_とDISTINCT
の両方がある場合、何か間違っています。このクエリを変更すると、パフォーマンスにどの程度の影響があるかは明らかではありません。
distinct
の不要な使用を中止してください。外側の適用サブクエリだけでなく、メインクエリ自体。
もう1つ試すことができるのは、外側の適用サブクエリを削除することです。代わりに、テーブルと一致条件を_left joins
_として追加します。メインクエリで、count(distinct student pk)
に変更します。
他にもいくつか試すことができますが、私はそれらの外側の適用に焦点を当てるか、少なくとも実行計画を見て、どこに焦点を当てるかを考えます。
チェックするもう1つの重要なことは、テーブルに適切なインデックスがあることです。結果の数を考慮すると、インデックスが欠落している可能性があります。
あなたの計画にはたくさんのテーブルスキャンがあることに気づきました。頻繁に結合している列にインデックスを追加することを検討してください。一般的にフィルターをかける場合は、これらの日付にも追加してください。
基になるテーブルを変更する必要があります。インデックス付きビューを作成できますが、これには多くの制限があり、インデックステーブルの代わりにはなりません。ビューにインデックスを付けることは、パフォーマンスの負担をselect
s(読み取り)データからinsert
s、update
s、およびdelete
s(書き込み)に移動する良い方法です。ディスク容量を犠牲にしてデータ。驚くべきパフォーマンス上の利点がありますが、コストはかかりません。
Joe Obbishの回答 はクエリプランに対してより完全な応答を示します。
CTE
は不要ですselect
なしでwhere
表を直接使用するだけ
Select [rost].[Teacher] As Teacher
, [Team Name] = fc.team
, [fc] = ISNULL(fc, 0)
, [1st Count] = count(b.[students])
, [2nd Count] = count(c.potstudents)
, [TIA] = ROUND(SUM(ISNULL(ia.tia, 0)), 0)
, [SUM] = CAST(ROUND(SUM(ISNULL(inb.tinb, 0)) + SUM(ISNULL(rip.pit, 0)), 0) As INT)
, [YR] = ROUND(SUM(ISNULL(tr.totamt, 0)), 0)
FROM dbo.roster rost
LEFT JOIN fc
ON fc.Teacher = rost.Teacher
LEFT JOIN ia
ON ia.Teacher = rost.Teacher
LEFT JOIN inb inb
ON inb.Teacher = rost.Teacher
LEFT JOIN ripcord rip
ON rip.Teacher = rost.Teacher
LEFT JOIN YRTR tr
ON tr.Teacher = rost.Teacher
LEFT JOIN students b
on b.Teacher = rost.Teacher
AND b.regdate >= @Startdate
AND b.regdate < DATEADD(DAY,1,@Enddate)
LEFT JOIN dbo.potstudents c
ON c.Teacher = rost.Teacher
AND c.returndate >= @Startdate
AND c.returndate < DATEADD(DAY,1,@Enddate)
GROUP BY rost.[Teacher], fc.TEAM, fc.fc
ORDER BY rost.[Teacher] ASC