クエリの一部が長時間CPUを使い果たしているのは、GROUP BY句の機能と、このインスタンスでは常にインデックスなしの並べ替えが必要になるという事実です。タイムスタンプフィールドのインデックスは、最初のフィルターに役立ちますが、フィルターが一致するすべての行でこの操作を実行する必要があります。これを高速化すると、より効率的なルートを使用して、Alexが提案するのと同じジョブを実行できますが、クエリプランナーを使用する関数の組み合わせが思い付かないため、依然として非常に非効率です。インデックスの助けとなるものであるため、最初に関数を実行してグループ化値を計算するすべての行を実行する必要があり、その後のみデータを順序付け、結果のグループ化に対して集計を計算できます。
そのため、解決策は、何らかの方法でインデックスを使用できるプロセスグループを作成するか、一致するすべての行を一度に考慮する必要をなくすことです。
時間に丸められた時間を含む行ごとに追加の列を維持し、そのようなクエリで使用するためにこの列にインデックスを付けることができます。これはデータの非正規化であるため、「ダーティ」な感じがするかもしれませんが、将来の使用のためにすべての集計をキャッシュする(およびベースデータが変更されるとそのキャッシュを更新する)よりもきれいになります。追加の列は、データを挿入したり、タイムスタンプ列または既存の行を更新する可能性のある現在および将来のすべての場所で新しいデータの一貫性のあるデータを保証するため、他の場所のロジックではなく、トリガーまたは永続的な計算列で維持する必要がありますカラム。MIN(タイムスタンプ)は引き続き取得できます。この方法でクエリが発生するのは、まだすべての行をたどることです(これは避けられないことは明らかです)が、インデックスの順序はできますが、グループ化/集計を実行する前に、インデックスなしの並べ替え操作の行セット全体を覚える必要はなく、インデックス内の次の値に到達するたびに各グループ化の行を出力します。また、以前にグループ化した値の行を覚えておく必要がないため、現在使用している行または残りの行を処理するため、メモリの使用量も少なくなります。
そのメソッドは、結果セット全体のメモリのどこかを見つける必要性を取り除き、グループ操作のインデックスなしソートを行い、大きなクエリからグループ値の計算を削除します(そのジョブを生成する個々のINSERT / UPDATEに移動します)データ)、集計された結果の個別のストアを維持する必要なく、そのようなクエリを許容できる程度に実行できるようにする必要があります。
しない方法データを非正規化しますが、それでも追加の構造が必要な場合は、「タイムテーブル」を使用します。この場合、考慮する可能性のあるすべての時間について1時間に1行が含まれます。このテーブルは、DBまたはかなりのサイズで大量のスペースを消費しません-100年のタイムスパンをカバーするために、2つの日付(「2011-01-01 @ 00:00:00.0000 '、' 2011-01-01 @ 00:00:59.9997 '、 "9997"は、DATETIMEフィールドが次の秒に切り上げられない最小ミリ秒数です)クラスター化された主キーには、最大14 MBのスペースが必要です(行ごとに8 + 8バイト* 24時間/日* 365.25日/年* 100に加えて、クラスター化インデックスのツリー構造のオーバーヘッド用のビットが必要ですが、そのオーバーヘッドは大きくありません) 。
SELECT CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour
, MIN([timestamp]) as TimeStamp
, AVG(MyField) As AvgField
FROM TimeRangeByHours tt
INNER JOIN MyData md ON md.TimeStamp BETWEEN tt.StartTime AND tt.EndTime
WHERE tt.StartTime > '4/10/2011'
GROUP BY tt.StartTime
ORDER BY tt.StartTime
これは、クエリプランナーが使用するMyData.TimeStampのインデックスを調整できることを意味します。クエリプランナーは、MyData.TimeStampのインデックスと調和して飼いならされたテーブルをたどることができるように十分に明るく、グループ化ごとに1行を出力し、次のグループ化値に達すると各セットまたは行を破棄する必要があります。すべての中間行をRAMのどこかに保存してから、インデックスなしの並べ替えを実行することはありません。もちろん、この方法ではタイムテーブルを作成し、前後に十分に及ぶことを確認する必要がありますが、タイムテーブルを使用して、さまざまなクエリの多くの日付フィールドに対するクエリを使用できます。この方法でフィルタリング/グループ化する必要がある各日付フィールドの追加の計算列、およびテーブルのサイズが小さい(10に及ぶ必要がない限り)
タイムテーブルメソッドには、現在の状況と計算列ソリューションと比較して(非常に有利な場合があります)余分な違いがあります。上記のクエリ例のINNER JOINを変更するだけで、データがない期間の行を返すことができます左外側になります。
一部の人々は、物理的なタイムテーブルを持たず、代わりに常にテーブルを返す関数からそれを返すことを提案します。これは、タイムテーブルの内容がディスクに保存されない(またはディスクから読み取る必要がない)ことを意味し、関数が適切に記述されていれば、タイムテーブルが時間内を行き来する時間を心配する必要はありませんが、すべてのクエリでメモリ内テーブルを作成するためのCPUコストは、物理タイムテーブルを作成する(および、初期バージョンの制限を超えてタイムスパンを延長する必要がある場合に維持する)面倒な労力を少し節約する価値があります。
補足説明:元のクエリにもDISTINCT句は必要ありません。グループ化により、これらのクエリは考慮中の期間ごとに1行のみを返すようになるため、DISTINCTはCPUをもう少しスピンする以外は何もしません(クエリプランナーが、この区別がノーオペレーションであることに気付かない限り)無視して、余分なCPU時間を使用しないでください)。