Stundenweise Gruppierung über einen großen Datensatz


12

Unter Verwendung von MS SQL 2008 wähle ich ein gemitteltes Feld aus 2,5 Millionen Datensätzen aus. Jeder Datensatz entspricht einer Sekunde. MyField ist ein stündlicher Durchschnitt dieser 1-Sekunden-Datensätze. Natürlich trifft die Server-CPU 100% und die Auswahl dauert zu lange. Ich muss möglicherweise diese gemittelten Werte speichern, damit SQL nicht alle diese Datensätze bei jeder Anforderung auswählen muss. Was kann getan werden?

  SELECT DISTINCT
         CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR)
ORDER BY TimeStamp

6
Ist TimeStamp Teil eines Clustered-Index? Es sollte sein ...

@antisanity - warum? Er schöpft die CPU aus, nicht die Festplatte
Jack sagt, versuchen Sie topanswers.xyz

Antworten:


5

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).


3

Sehen Sie sich diese Frage an ( ein Datum festlegen). Warum sollten Sie sich auch die Mühe machen, alles in einen String umzuwandeln? Das können Sie später tun (falls erforderlich).

  SELECT DISTINCT
         dateadd(hour,datediff(hour,0,[timestamp]),0) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY dateadd(hour,datediff(hour,0,[timestamp],0);
ORDER BY TimeStamp

1

Möchten Sie die Abfrage beschleunigen oder möchten Sie wissen, wie ein Datenschnappschuss erstellt und gespeichert werden soll?

Wenn Sie es schneller machen möchten, benötigen Sie auf jeden Fall einen Index für das TimeStamp-Feld. Außerdem würde ich vorschlagen, dies für die Umrechnung in Stunden zu verwenden:

select convert(varchar(13), getdate(), 121)

Wenn Sie einen Schnappschuss insert intoerstellen und später wiederverwenden müssen , erstellen Sie mithilfe von eine neue Tabelle mit den Ergebnissen Ihrer Abfrage. Indextabelle entsprechend und verwenden Sie es. Soweit ich weiß, benötigen Sie einen Index für TimeStampHour.

Sie können auch einen Job einrichten, der die täglichen Daten in Ihrer neuen Aggregattabelle aggregiert.


-1

Indem Sie Ihre group by -Klausel in eine solche Zeichenfolge umwandeln, machen Sie sie im Wesentlichen zu einem nicht indizierten Treffer für jede einzelne Zeile in der Datenbank. Dies ist, was Ihre Leistung tötet. Jeder halbwegs vernünftige Server kann ein einfaches Aggregat wie dieses für eine Million Datensätze verarbeiten, wenn die Indizes ordnungsgemäß verwendet werden. Ich würde Ihre Abfrage ändern und einen Clustered-Index für Ihre Zeitstempel erstellen. Das wird Ihr Leistungsproblem lösen, während das Berechnen der Daten jede Stunde nur das Problem verschiebt.


1
-1 - nicht sind Sie nicht „es einen nicht indizierten Treffer auf jede einzelne Zeile in der Datenbank zu machen“ - jeder Index TimeStampwird nach wie vor verwendet werden , um die Zeilen zu filtern
Jack sagt Versuch topanswers.xyz

-3

Ich würde in Betracht ziehen, die Idee aufzugeben, diese Art der Berechnung unter Verwendung eines relationalen Datenbankmodells zu implementieren. Besonders wenn Sie viele Datenpunkte haben, für die Sie sekündlich Werte sammeln.

Wenn Sie das Geld haben, können Sie einen dedizierten Prozessdatenhistoriker wie den folgenden erwerben:

  1. Honeywell Uniformance PHD
  2. Osisoft PI
  3. Aspentech IP21
  4. etc.

Diese Produkte können riesige Mengen von unglaublich dichten Zeitreihendaten (in proprietären Formaten) speichern und gleichzeitig die schnelle Verarbeitung von Datenextraktionsabfragen ermöglichen. Die Abfragen können viele Datenpunkte (auch als Tags bezeichnet), lange Zeitintervalle (Monate / Jahre) und darüber hinaus eine Vielzahl von Berechnungen zusammenfassender Daten (einschließlich Durchschnittswerte) enthalten.

.. und im Allgemeinen: Ich versuche immer, das DISTINCTSchlüsselwort beim Schreiben von SQL zu vermeiden . Es ist kaum eine gute Idee. In Ihrem Fall sollten Sie in der Lage sein DISTINCT, die gleichen Ergebnisse zu erzielen MIN([timestamp]), indem Sie Ihre GROUP BYKlausel ergänzen.


1
Das ist nicht wirklich genau. Eine relationale Datenbank ist für 2,5 Millionen Datensätze völlig in Ordnung. Und er macht nicht einmal Joins an vielen Tischen. Der erste Hinweis darauf, dass Sie entweder Ihre Daten denormalisieren oder auf ein nicht relationales System umsteigen müssen, ist, wenn Sie große, komplexe Verknüpfungen über viele Tabellen ausführen. Der Datensatz des Posters klingt tatsächlich nach einer vollkommen akzeptablen Verwendung eines relationalen Datenbanksystems.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.