Beste Methode zum Aggregieren, Speichern und Verwenden von Daten in SQL Server (Trigger, geplante Jobs, SSAS?)

7

Wir haben eine Tabelle mit räumlichen Daten und einigen Attributen und müssen diese aggregieren, damit sie in verschiedenen Abfragen verwendet werden können. Wir haben den Mechanismus, um die Aggregation im laufenden Betrieb über gespeicherte CLR-Prozeduren durchzuführen, aber ich habe mich gefragt, wie diese aggregierten Daten in SQL Server 2008 R2 am besten zwischengespeichert werden können, da die Aggregation sehr langsam ist.

Wir haben also einen Tisch wie diesen:

[TableId] [int] NOT NULL,
[FeatureId] [int] NOT NULL,
[MeasureFrom] [float] NOT NULL,
[MeasureTo] [float] NOT NULL,
[Value] [smallint] NOT NULL,
[Timestamp] [datetime2](7) NOT NULL

FeatureId, MeasureFrom und MeasureTo definieren grundsätzlich ein Intervall und Rating ist der Wert dieses Intervalls. Beachten Sie, dass FeatureId tatsächlich auf reale Linien (Formen) aus einer anderen Tabelle verweist. In dieser Tabelle befinden sich derzeit ca. 250.000 Zeilen mit überlappenden Intervallen und verschiedenen Zeitstempeln (historische Aufzeichnungen). Unser Aggregationsprozess versucht, den neuesten Wert über alle Intervalle hinweg zu finden, sodass Intervalle basierend auf dem Zeitstempel gekürzt und verknüpft werden müssen und der zugehörige Wert beibehalten wird.

Da die direkte Abfrage langsam ist und viele Ressourcen beansprucht, dachten wir, wir müssten möglicherweise eine weitere Datenbank erstellen, in der die Ergebnisse dieser Aggregation gespeichert werden. Die Quelltabelle wird ständig aktualisiert (aber nicht zu oft, einmal in ein paar Tagen).

Was wäre der beste Weg, um diese Datenbank zu erstellen und die aggregierten Werte auf dem neuesten Stand zu halten? Ich kann mir vorstellen, Trigger oder geplante Aufgaben zu verwenden. Ich habe keine Erfahrung mit SSAS, aber wäre das besser geeignet?

Zu beachten ist, dass wir einige ähnliche Tabellen haben, in denen leicht unterschiedliche Werte gespeichert sind (einige mit mehr als einer Wertespalte) und daher der Grund für eine separate Datenbank anstelle einer anderen Tabelle in der Originaldatenbank.

Wie schnell würde die Abfrage dieser separaten Datenbank von der ursprünglichen Datenbank mithilfe datenbankübergreifender Verknüpfungen erfolgen?

Bearbeitet: Um zu demonstrieren, was unsere "Aggregation" bewirkt, hier einige Beispieldaten:

FeatureId | MeasureFrom | MeasureTo | Value | Timestamp
1         | 1           | 20        | 2     | 2015-01-01
1         | 5           | 15        | 3     | 2015-01-02
1         | 9           | 10        | 8     | 2015-01-03

Und das Ergebnis, das wir bekommen:

FeatureId | MeasureFrom | MeasureTo | Value | Timestamp
1         | 1           | 5         | 2     | 2015-01-01
1         | 5           | 9         | 3     | 2015-01-02
1         | 9           | 10        | 8     | 2015-01-03
1         | 10          | 15        | 3     | 2015-01-02
1         | 15          | 20        | 2     | 2015-01-01

Wie Sie sehen können, enthält das Ergebnis tatsächlich mehr Zeilen als die Originaldaten. Grundsätzlich benötigen wir den neuesten Wert an jedem Punkt innerhalb des Gebiets. Der letzte Wert wird basierend auf dem Wert in der Spalte Zeitstempel ermittelt. Wenn innerhalb von Intervallen eine Lücke besteht, sollte diese auf den resultierenden Datensatz übertragen werden.

Der Code ist tatsächlich in .NET geschrieben und ziemlich kompliziert. Der Algorithmus lautet wie folgt: Alle Daten für ein Merkmal von Interesse abrufen, die Datensätze nach absteigendem Zeitstempel sortieren, alle Datensätze in einer Schleife verarbeiten. Nehmen Sie das Intervall (MeasureFrom, MeasureTo) und schneiden Sie es mit dem Ergebnis, das Sie haben. Wenn Sie einen Teil erhalten, den Sie noch nicht haben, fügen Sie ihn dem Ergebnis hinzu. Fahren Sie mit dem nächsten Datensatz fort. Daten stellen Umfragen dar, die regelmäßig durchgeführt werden. Je weiter Sie in der Zeit gehen, desto mehr Daten werden weggeworfen, da Sie bereits einen neueren Wert für dasselbe Intervall haben.

Umfragen werden mit zufälligen Zeitstempeln geliefert, sodass nicht immer die neuesten eingehen. Der gleiche Vorgang wird dann auch für andere Funktionen wiederholt, da Sie möglicherweise daran interessiert sind, Werte für mehrere Funktionen gleichzeitig abzurufen. Und dies ist das Ergebnis, das die Leute oft abfragen würden.

Idealerweise möchten wir das Aggregationsergebnis so nah wie möglich an der Echtzeit halten.

Weitere Informationen zum erforderlichen Algorithmus finden Sie unter dem Link von @dnoeth (im Kommentar unten) http://sqlmag.com/sql-server/packing-intervals-priorities . Es löst ein ähnliches Problem mit dem Unterschied, Prioritäten anstelle von Zeitstempeln zu verwenden, und die beschriebene Lösung verwendet Funktionen aus SQL Server 2012.

Jandic
quelle
2
einmal in ein paar Tagen wird nicht ständig aktualisiert :-) Scheint ein Batch-Update zu sein, daher sollte das Erstellen der vorberechneten Daten als letzter Schritt des Batches der beste Weg sein. Übrigens ist es ein ähnliches Problem wie bei sqlmag.com/sql-server/packing-intervals-priorities , aber die Lösung von Itzik basiert auf Funktionen, die in SS2008 nicht vorhanden sind ...
am
Ist die Bestellung wichtig und warum? Stimmen Sie das fehlende Intervall nur mit vorhergehenden oder folgenden Werten oder einem von beiden überein?
Julien Vavasseur
Ja, die Reihenfolge ist wichtig. Wir müssen den neuesten Wert basierend auf dem Zeitstempel erhalten, wie in meiner Frage beschrieben. Wenn es eine Lücke gibt (fehlendes Intervall), sollte die Lücke auch im Ergebnis erscheinen.
Jandic
Lücke und fehlendes Intervall kommen nicht in Frage und ich habe es nicht berücksichtigt. Vielleicht können Sie die Frage mit dieser neuen Anforderung bearbeiten und Beispiel und Ausgabe mit einer Lücke hinzufügen
Julien Vavasseur
@dnoeth: danke für den Link, dies ist sehr ähnlich zu der Lösung, die ich in .NET geschrieben habe, abgesehen von Zeitstempeln anstelle von Prioritäten, was die Dinge komplexer macht
jandic

Antworten:

3

Die Aggregattabelle kann wahrscheinlich in derselben Datenbank verbleiben. Sie können es jedoch auf einer separaten Dateigruppe und Festplatte erstellen.

Vollständige Aktualisierung einer neuen Aggregattabelle

Eine Möglichkeit, dies mit reinem SQL und Ihrem Beispiel zu tun:

WITH list ([Type], [FeatureId], [Measure], [Value], [Timestamp], [ID]) as (
    SELECT *
        , ROW_NUMBER() OVER(PARTITION BY [FeatureId] ORDER BY [Measure]) 
    FROM (
        SELECT 0, [FeatureId], [MeasureFrom], [Value], [Timestamp] FROM data
        UNION ALL
        SELECT 1, [FeatureId], [MeasureTo], [Value], [Timestamp] FROM data
    ) l([Type], [FeatureId], [Measure], [Value], [Timestamp])
)
SELECT l1.FeatureId, [MeasureFrom] = l1.Measure, [MeasureTo] = l2.Measure
    , [Value] = CASE WHEN l1.Type = 0 THEN l1.Value ELSE l2.Value END
    , [Timestamp] = CASE WHEN l1.Type = 0 THEN l1.Timestamp ELSE l2.Timestamp END
FROM list l1
INNER JOIN list l2 ON l1.FeatureId = l2.FeatureId AND l1.ID+1 = l2.ID
;

Bei dieser wirklich kleinen Stichprobe bin ich mir nicht sicher, ob sie alle Ihre Bedürfnisse abdeckt. Es kann hilfreich sein, eine größere Stichprobe mit mehr Daten hinzuzufügen.

Es werden 4 Tabellenscans durchgeführt. Da Sie 250.000 Zeilen haben, ist die Leistung möglicherweise nicht so gut. Es ist wahrscheinlich besser, es in einem Stapel von X aufeinanderfolgenden FeatureIds auszuführen.

Dies kann mithilfe eines Jobs und eines SSIS-Pakets mit einem einzelnen Update oder einem Batch-Update erfolgen. Sie müssten zuerst die Aggregattabelle abschneiden.

Auslöser für jede neue Zeile in der Haupttabelle

Für neue Zeilen, die einen Trigger verwenden, kann diese Abfrage verwendet werden:

WITH new([FeatureId], [MeasureFrom], [MeasureTo], [Value], [Timestamp]) as (
--    SELECT 1, 1, 20, 2, '2015-01-01'
--  SELECT 1, 5, 15, 3, '2015-01-02'
    SELECT 1, 9, 10, 8, '2015-01-03'
), gap([FeatureId], mn, mx) as (
    SELECT n.[FeatureId], mn.mn, mx.mx
    FROM new n
    CROSS APPLY (SELECT mn = MAX([MeasureFrom]) FROM data3 WHERE [FeatureId] = n.[FeatureId] AND [MeasureFrom] < n.MeasureFrom) mn
    CROSS APPLY (SELECT mx = MIN([MeasureTo]) FROM data3 WHERE [FeatureId] = n.[FeatureId] AND [MeasureTo] > n.MeasureTo) mx
), list (x,[FeatureId], [Measure], [Value], [Timestamp], [ID]) as (
    SELECT *
        , ROW_NUMBER() OVER(PARTITION BY [FeatureId] ORDER BY [Measure]) 
    FROM (
        SELECT 0, d.[FeatureId], d.[MeasureFrom], d.[Value], d.[Timestamp] 
        FROM data3 d
        INNER JOIN gap g ON d.[FeatureId] = g.[FeatureId] AND (d.MeasureFrom >= g.mn AND d.MeasureFrom < g.mx)
        UNION ALL
        SELECT 1, d.[FeatureId], d.[MeasureTo], d.[Value], d.[Timestamp] 
        FROM data3 d
        INNER JOIN gap g ON d.[FeatureId] = g.[FeatureId] AND d.MeasureTo = g.mx
        UNION ALL
        SELECT 2, [FeatureId], [MeasureFrom], [Value], [Timestamp] FROM new
        UNION ALL
        SELECT 3, [FeatureId], [MeasureTo], [Value], [Timestamp] FROM new
    ) l(x, [FeatureId], [Measure], [Value], [Timestamp])
) 
MERGE data3 AS target
USING (
    SELECT l1.FeatureId, [MeasureFrom] = l1.Measure, [MeasureTo] = l2.Measure
        , [Value] = COALESCE(d.[Value], l1.[Value])
        , [Timestamp] = COALESCE(d.[Timestamp], l1.[Timestamp])
    FROM list l1
    INNER JOIN list l2 ON l1.FeatureId = l2.FeatureId AND l1.ID+1 = l2.ID
    LEFT JOIN data3 d ON d.MeasureFrom = l1.Measure OR d.MeasureTo = l2.Measure
) as source(FeatureId, [MeasureFrom], [MeasureTo], [Value], [Timestamp])
ON (target.FeatureId = source.FeatureId AND target.[MeasureFrom] = source.[MeasureFrom])
WHEN MATCHED THEN
    UPDATE SET target.[MeasureTo] = source.[MeasureTo]
WHEN NOT MATCHED BY target THEN
    INSERT (FeatureId, [MeasureFrom], [MeasureTo], [Value], [Timestamp])
    VALUES (source.FeatureId, source.[MeasureFrom], source.[MeasureTo], source.[Value], source.[Timestamp])
;

Sie müssen den neuen CTE durch die Werte der neu eingefügten Zeile im Trigger ersetzen und zum Zusammenführen mit der Aggregattabelle verwenden.

Geplante Aktualisierung neu hinzugefügter Zeilen

Wenn Sie eine Möglichkeit finden, eine Liste aller neu hinzugefügten Zeilen zur Haupttabelle seit der letzten Aktualisierung der Aggregattabelle abzurufen, können Sie einen Job alle x Minuten oder Stunden planen und nur das aktualisieren, was auf der Grundlage der letzten Daten erforderlich ist hinzugefügt.

Die Trigger-Abfrage funktioniert auch bei geplanten Updates.

Dies kann als geplanter Job und innerhalb eines SSIS-Pakets ausgeführt werden.

Julien Vavasseur
quelle
Schöne Zusammenfassung der Optionen, aber die Demos spiegeln nicht die Spalte "Zeitstempel" wider, sodass das Ergebnis nicht die neuesten Werte enthält
jandic
Können Sie ein größeres Sample und eine größere Ausgabe hinzufügen? Mit dieser kleinen Probe funktioniert es gut. Ich ändere sowieso ein paar Dinge, weil ich ein kleines Problem gefunden habe
Julien Vavasseur
1

Das Lesen / Verknüpfen aus mehreren Datenbanken ist wahrscheinlich nicht schneller als das Belassen der Daten in einer Tabelle in der Primärdatenbank, es sei denn, die zweite Datenbank befindet sich auf einer separaten physischen Festplatte. Denken Sie daran, dass die "Kosten" die meiste Zeit in der Festplatten-E / A liegen. Um die Arbeit in diesem Fall zu beschleunigen, sollten Sie versuchen, die Festplatten-E / A zu verringern oder zu verteilen. Dies wird durch Erstellen von Indizes, Partitionieren der Tabellen auf verschiedene Datenträger oder Verschieben Ihrer Aggregattabelle auf einen anderen Datenträger oder eine andere Datenbank erreicht.

Es hört sich so an, als würden Sie dazu neigen, Dinge nach einem Zeitplan zu migrieren oder zu speichern. SSAS kann dies möglicherweise erreichen (ich weiß nicht), aber es kann schwierig sein, jemanden zu finden, der einigermaßen gut in SSAS ist. Auf der anderen Seite können Sie einen Job einfach über SSIS planen (dies ist möglicherweise das, was Sie gemeint haben; SSAS ist Analysis Services und arbeitet mit Cubes; SSIS ist Integration Services und dient eher zum Transformieren und Verschieben von Daten zwischen Quellen, z. B. zum Extrahieren von Tabellen oder Inhalte in eine CSV- oder Excel-Datei anzeigen.)

Fred Shope
quelle
Nun, für eine geplante Aufgabe habe ich überlegt, SSIS oder ähnliches zu verwenden, aber ich habe mich gefragt, ob SSAS nicht besser geeignet wäre. Wir möchten die Verwendung von Triggern vermeiden, da dies den Datenladevorgang verlangsamen würde (da der "Aggregations" -Prozess eine Weile dauert) und eine geplante Aufgabe eine gewisse Verzögerung zwischen dem Laden der Daten und der Verfügbarkeit der "aggregierten" Daten einführt. Ich habe noch nie SSAS verwendet. Ich habe jemanden um seine Meinung gebeten, der es weiß. Idealerweise möchten wir das Aggregationsergebnis so nah wie möglich an der Echtzeit halten.
Jandic