Der Teil der Abfrage besteht darin, die CPU für lange Zeiträume zu maximieren. Dies betrifft die Funktionen in der GROUP BY-Klausel und die Tatsache, dass für die Gruppierung in diesem Fall immer eine nicht indizierte Sortierung erforderlich ist. Ein Index im Zeitstempelfeld hilft zwar beim erstmaligen Filtern, dieser Vorgang muss jedoch für jede Zeile ausgeführt werden, mit der der Filter übereinstimmt. Wenn Sie dies beschleunigen, hilft es, eine effizientere Route zu verwenden, um dieselbe Aufgabe wie von Alex vorgeschlagen zu erledigen, aber Sie haben dort immer noch eine enorme Ineffizienz, da die im Abfrageplaner verwendete Funktionskombination nicht in der Lage sein wird, eine solche zu finden Etwas, das von jedem Index unterstützt wird, sodass er jede Zeile durchlaufen muss, in der zuerst die Funktionen ausgeführt werden, um die Gruppierungswerte zu berechnen. Erst dann kann er die Daten sortieren und die Aggregate über die resultierenden Gruppierungen berechnen.
Die Lösung besteht also darin, die Prozessgruppe durch einen Index zu formen, für den sie einen Index verwenden kann, oder auf andere Weise die Notwendigkeit zu beseitigen, alle übereinstimmenden Zeilen auf einmal zu berücksichtigen.
Sie können für jede Zeile, die die auf die Stunde gerundete Zeit enthält, eine zusätzliche Spalte pflegen und diese Spalte für die Verwendung in solchen Abfragen indizieren. Dies denormalisiert Ihre Daten, so dass Sie sich möglicherweise "schmutzig" fühlen, aber es würde funktionieren und sauberer sein, als alle Aggregate für die zukünftige Verwendung zwischenzuspeichern (und diesen Cache zu aktualisieren, wenn die Basisdaten geändert werden). Die zusätzliche Spalte sollte vom Trigger verwaltet werden oder eine dauerhaft berechnete Spalte sein und nicht von einer anderen Logik, da dies garantiert, dass alle aktuellen und zukünftigen Stellen, an denen Daten eingefügt oder die Zeitstempelspalten oder vorhandenen Zeilen aktualisiert werden, zu konsistenten Daten in der neuen Spalte führen Säule. Sie können immer noch den MIN (Zeitstempel) ausgeben. Was die Abfrage auf diese Weise ergibt, ist immer noch ein Spaziergang durch alle Zeilen (dies kann natürlich nicht vermieden werden), aber es kann die Indexreihenfolge ändern. Ausgeben einer Zeile für jede Gruppierung, wenn der nächste Wert im Index erreicht wird, anstatt sich die gesamte Reihe von Zeilen für eine nicht indizierte Sortieroperation merken zu müssen, bevor die Gruppierung / Aggregation ausgeführt werden kann. Es wird auch viel weniger Speicherplatz verbrauchen, da es nicht erforderlich ist, Zeilen aus vorherigen Gruppierungswerten zu speichern, um die gerade betrachtete oder die restlichen zu verarbeiten.
Diese Methode beseitigt die Notwendigkeit, für die gesamte Ergebnismenge irgendwo im Speicher zu suchen, und führt die nicht indizierte Sortierung für die Gruppenoperation durch und entfernt die Berechnung der Gruppenwerte aus der großen Abfrage (Verschieben dieses Jobs zu den einzelnen INSERTs / UPDATEs, die das erzeugen) Daten) und sollte ermöglichen, dass solche Abfragen akzeptabel ausgeführt werden, ohne dass ein separater Speicher für die aggregierten Ergebnisse geführt werden muss.
Eine Methode, die es nicht tutWenn Sie Ihre Daten denormalisieren, aber dennoch eine zusätzliche Struktur benötigen, müssen Sie einen "Zeitplan" verwenden, in diesem Fall einen, der eine Zeile pro Stunde für die gesamte Zeit enthält, die Sie wahrscheinlich in Betracht ziehen. Diese Tabelle würde in einer Datenbank oder einer nennenswerten Größe nicht viel Speicherplatz beanspruchen - um eine Zeitspanne von 100 Jahren abzudecken, enthält eine Tabelle eine Zeile mit zwei Datumsangaben (Beginn und Ende der Stunde, z. B. '2011-01-01 @ 00: 00: 00.0000 ',' 2011-01-01 @ 00: 00: 59.9997 ', wobei "9997" die kleinste Anzahl von Millisekunden ist, die ein DATETIME-Feld nicht auf die nächste Sekunde aufrundet Der geclusterte Primärschlüssel benötigt ca. 14 MB Speicherplatz (8 + 8 Byte pro Zeile * 24 Stunden / Tag * 365,25 Tage / Jahr * 100 plus ein bisschen Overhead für die Baumstruktur des geclusterten Index, dieser Overhead ist jedoch nicht signifikant). .
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
Dies bedeutet, dass der Abfrageplaner veranlassen kann, dass der Index für MyData.TimeStamp verwendet wird. Der Abfrageplaner sollte hell genug sein, um zu ermitteln, ob er die zahme Tabelle in Übereinstimmung mit dem Index für MyData.TimeStamp durchgehen kann. Dabei wird erneut eine Zeile pro Gruppierung ausgegeben und jede Gruppe oder Zeilen verworfen, sobald der nächste Gruppierungswert erreicht wird. Es müssen nicht alle Zwischenzeilen irgendwo im RAM gespeichert werden, sondern es muss eine nicht indizierte Sortierung durchgeführt werden. Natürlich erfordert diese Methode, dass Sie den Zeitplan erstellen und sicherstellen, dass er sich sowohl vorwärts als auch rückwärts über einen ausreichenden Abstand erstreckt. Sie können den Zeitplan jedoch für Abfragen mit vielen Datumsfeldern in verschiedenen Abfragen verwenden, für die die Option "zusätzliche Spalte" erforderlich wäre eine extra berechnete Spalte für jedes Datumsfeld, das Sie auf diese Weise filtern / gruppieren mussten, und die geringe Größe der Tabelle (es sei denn, Sie benötigen 10,
Die Zeittabellenmethode hat einen zusätzlichen Unterschied (der durchaus von Vorteil sein könnte) zu Ihrer aktuellen Situation und der berechneten Spaltenlösung: Sie kann Zeilen für Zeiträume zurückgeben, für die keine Daten vorhanden sind, indem Sie einfach den INNER JOIN in der obigen Beispielabfrage ändern links außen sein.
Einige Leute schlagen vor, keinen physischen Zeitplan zu haben, sondern ihn immer von einer Tabellenrückgabefunktion zurückzugeben. Dies bedeutet, dass der Inhalt des Zeitplans niemals auf der Festplatte gespeichert ist (oder von dieser gelesen werden muss). Wenn die Funktion gut geschrieben ist, müssen Sie sich keine Gedanken darüber machen, wie lange der Zeitplan in der Zeit hin und her laufen muss, aber ich Zweifelsohne sind die CPU-Kosten für die Erstellung einer speicherinternen Tabelle für einige Zeilen jeder Abfrage die geringe Einsparung von Aufwand beim Erstellen (und Verwalten, sollte die Zeitspanne über das Limit Ihrer ursprünglichen Version hinausgehen) des physischen Zeitplans wert.
Eine Randnotiz: Sie brauchen diese DISTINCT-Klausel auch nicht in Ihrer ursprünglichen Abfrage. Durch die Gruppierung wird sichergestellt, dass diese Abfragen nur eine Zeile pro betrachteten Zeitraum zurückgeben, sodass DISTINCT nur die CPU ein wenig mehr dreht (es sei denn, der Abfrageplaner merkt, dass der Unterschied ein No-Op ist. In diesem Fall ist dies der Fall ignorieren und keine zusätzliche CPU-Zeit verwenden).