it-swarm.com.de

Bestes Datenbank- und Tabellendesign für Milliarden von Datenzeilen

Ich schreibe eine Anwendung, die große Mengen an elektrischen Daten und Temperaturdaten speichern und analysieren muss.

Grundsätzlich muss ich große Mengen stündlicher Stromverbrauchsmessungen für die letzten Jahre und für viele Jahre für Zehntausende von Standorten speichern und dann die Daten auf nicht sehr komplexe Weise analysieren.

Die Informationen, die ich (vorerst) speichern muss, sind Standort-ID, Zeitstempel (Datum und Uhrzeit), Temperatur und Stromverbrauch.

Über die Menge der Daten, die gespeichert werden müssen, ist dies eine Annäherung, aber etwas in dieser Richtung:
Über 20 000 Standorte, 720 Aufzeichnungen pro Monat (stündliche Messungen, ungefähr 720 Stunden pro Monat), 120 Monate (vor 10 Jahren) und viele Jahre in der Zukunft. Einfache Berechnungen ergeben folgende Ergebnisse:

20 000 Standorte x 720 Datensätze x 120 Monate (vor 10 Jahren) = 1 728 000 000 Datensätze.

Dies sind die vergangenen Datensätze. Neue Datensätze werden monatlich importiert. Das sind also ungefähr 20 000 x 720 = 14 400 000 neue Datensätze pro Monat.

Auch die Gesamtzahl der Standorte wird stetig wachsen.

Für alle diese Daten müssen die folgenden Vorgänge ausgeführt werden:

  1. Abrufen der Daten für ein bestimmtes Datum UND einen bestimmten Zeitraum: Alle Datensätze für eine bestimmte Standort-ID zwischen dem Datum 01.01.2013 und 01.01.2017 sowie zwischen 07:00 und 13:00 Uhr.
  2. Einfache mathematische Operationen für einen bestimmten Datums- und Zeitbereich, z. MIN, MAX und AVG Temperatur und Stromverbrauch für eine bestimmte Standort-ID für 5 Jahre zwischen 07:00 und 13:00 Uhr.

Die Daten werden monatlich geschrieben, aber (mindestens) ständig von Hunderten von Benutzern gelesen, sodass die Lesegeschwindigkeit von wesentlich größerer Bedeutung ist.

Ich habe keine Erfahrung mit NoSQL-Datenbanken, aber nach dem, was ich gesammelt habe, sind sie die beste Lösung, um sie hier zu verwenden. Ich habe die beliebtesten NoSQL-Datenbanken gelesen, aber da sie sehr unterschiedlich sind und auch eine sehr unterschiedliche Tabellenarchitektur zulassen, konnte ich nicht entscheiden, welche Datenbank am besten zu verwenden ist.

Meine Hauptauswahl war Cassandra und MongoDB, aber da ich nur sehr begrenzte Kenntnisse und keine wirkliche Erfahrung in Bezug auf große Datenmengen und NoSQL habe, bin ich mir nicht sicher. Ich habe auch gelesen, dass PostreSQL auch behandelt solche Datenmengen gut.

Meine Fragen sind folgende:

  1. Sollte ich eine NoSQL-Datenbank für so große Datenmengen verwenden? Wenn nicht, kann ich mich an MySQL halten?
  2. Welche Datenbank soll ich verwenden?
  3. Sollte ich Datum und Uhrzeit in separaten, indizierten (wenn möglich) Spalten aufbewahren, um die Daten für bestimmte Zeit- und Datumsperioden schnell abzurufen und zu verarbeiten, oder kann dies erfolgen, indem der Zeitstempel in einer einzelnen Spalte gespeichert wird?
  4. Ist hier ein Ansatz zur Modellierung von Zeitreihendaten angemessen, und wenn nicht, können Sie mir Hinweise für ein gutes Tabellendesign geben?

Vielen Dank.

85
Gecata

Dies ist genau das, was ich jeden Tag mache, außer dass ich anstelle der stündlichen Daten die 5-Minuten-Daten verwende. Ich lade jeden Tag ungefähr 200 Millionen Datensätze herunter, daher ist die Menge, über die Sie hier sprechen, kein Problem. Die 5-Minuten-Daten haben eine Größe von ca. 2 TB) und ich habe Wetterdaten, die 50 Jahre lang stündlich nach Standort zurückreichen. Lassen Sie mich Ihnen also Fragen beantworten, die auf meinen Erfahrungen beruhen:

  1. Verwenden Sie hierfür kein NoSQL. Die Daten sind stark strukturiert und passen perfekt zu einer relationalen Datenbank.
  2. Ich persönlich verwende SQL Server 2016 und habe keine Probleme, Berechnungen auf dieses Datenvolumen anzuwenden. Es befand sich ursprünglich auf einer PostgreSQL-Instanz, als ich meinen Job startete, und es konnte das Datenvolumen nicht wie auf einer kleinen AWS-Instanz verarbeiten.
  3. Ich würde sehr empfehlen, den Stundenanteil des Datums zu extrahieren und getrennt vom Datum selbst zu speichern. Glauben Sie mir, lernen Sie aus meinen Fehlern!
  4. Ich speichere die meisten Daten in Bezug auf die Liste (DATE, TIME, DATAPOINT_ID, VALUE), aber so wollen die Leute die Daten nicht interpretieren. Seien Sie auf einige schreckliche Fragen zu den Daten und eine Menge Schwenkvorgänge vorbereitet. Haben Sie keine Angst davor, eine de-normalisierte Tabelle für Ergebnismengen zu erstellen, die einfach zu groß sind, um im laufenden Betrieb berechnet zu werden.

Allgemeiner Tipp: Ich speichere die meisten Daten zwischen zwei Datenbanken, die erste besteht aus direkten Zeitreihendaten und ist normalisiert. Meine zweite Datenbank ist sehr de-normalisiert und enthält voraggregierte Daten. So schnell mein System auch ist, ich bin nicht blind dafür, dass Benutzer nicht einmal 30 Sekunden auf das Laden eines Berichts warten möchten - auch wenn ich persönlich 30 Sekunden für Crunch 2 TB) denke = von Daten ist extrem schnell.

Um herauszufinden, warum ich empfehle, die Stunde getrennt vom Datum zu speichern, sind hier einige Gründe, warum ich das so mache:

  1. Die Darstellung der elektrischen Daten erfolgt durch Stundenende - daher ist 01:00 tatsächlich der Durchschnitt der elektrischen Leistung für die vorherige Stunde und 00:00 ist das Stundenende 24. (Dies ist wichtig weil Sie tatsächlich nach zwei Daten suchen müssen, um den 24-Stunden-Wert einzuschließen - den Tag, den Sie suchen, plus die erste Markierung des folgenden Tages.) Die Wetterdaten werden jedoch tatsächlich vorwärts dargestellt (tatsächlich und prognostiziert für die nächste Stunde). Nach meiner Erfahrung mit diesen Daten möchten Verbraucher die Auswirkungen des Wetters auf den Strompreis/die Strombedarf analysieren. Wenn Sie einen direkten Datumsvergleich verwenden würden, würden Sie tatsächlich den Durchschnittspreis für die vorherige Stunde mit der Durchschnittstemperatur für die folgende Stunde vergleichen, obwohl die Zeitstempel gleich sind. Wenn Sie die Stunde getrennt vom Datum speichern, können Sie Transformationen auf die Zeit anwenden, die weniger Auswirkungen auf die Leistung haben, als wenn Sie eine Berechnung auf eine DATETIME -Spalte anwenden würden.
  2. Performance. Ich würde sagen, dass mindestens 90% der von mir erstellten Berichte Diagramme sind, in denen normalerweise der Preis gegen die Stunde entweder für ein einzelnes Datum oder für eine Reihe von Daten aufgetragen wird. Wenn Sie die Zeit vom Datum trennen müssen, kann dies die Geschwindigkeit der Abfrage, mit der der Bericht erstellt wird, abhängig vom gewünschten Datumsbereich beeinträchtigen. Es ist nicht ungewöhnlich, dass Verbraucher in den letzten 30 Jahren ein einziges Datum im Jahresvergleich sehen möchten (tatsächlich ist dies für das Wetter erforderlich, um die 30-Jahres-Normalwerte zu generieren) - dies kann langsam sein. Natürlich können Sie Ihre Abfrage optimieren und Indizes hinzufügen. Vertrauen Sie mir, ich habe einige verrückte Indizes, die ich lieber nicht hätte, aber dadurch läuft das System schnell.
  3. Produktivität. Ich hasse es, mehr als einmal denselben Code schreiben zu müssen. Ich habe Datum und Uhrzeit in derselben Spalte gespeichert, bis ich immer wieder dieselbe Abfrage schreiben musste, um den Zeitanteil zu extrahieren. Nach einer Weile hatte ich es einfach satt, dies tun zu müssen und extrahierte es in eine eigene Spalte. Je weniger Code Sie schreiben müssen, desto geringer ist die Wahrscheinlichkeit eines Fehlers. Wenn Sie weniger Code schreiben müssen, können Sie Ihre Berichte schneller veröffentlichen. Niemand möchte den ganzen Tag auf Berichte warten.
  4. Endnutzer. Nicht alle Endbenutzer sind Hauptbenutzer (d. H. Sie wissen, wie man SQL schreibt). Wenn Sie die Daten bereits in einem Format gespeichert haben, das sie mit minimalem Aufwand in Excel (oder ein ähnliches Tool) einbinden können, werden Sie zu einem Helden im Büro. Wenn die Benutzer nicht einfach auf die Daten zugreifen oder diese bearbeiten können, verwenden sie Ihr System nicht. Glauben Sie mir, ich habe vor ein paar Jahren das perfekte System entworfen und aus diesem Grund hat es niemand benutzt. Beim Datenbankdesign geht es nicht nur darum, vordefinierte Regeln/Richtlinien einzuhalten, sondern das System nutzbar zu machen.

Wie ich oben sagte, basiert dies alles auf meiner persönlichen Erfahrung, und ich möchte Ihnen sagen, dass es einige schwierige Jahre und viele Neugestaltungen waren, bis ich dort bin, wo ich jetzt bin. Tun Sie nicht das, was ich getan habe, lernen Sie aus meinen Fehlern und stellen Sie sicher, dass Sie die Endbenutzer Ihres Systems (oder Entwickler, Berichtsautoren usw.) in Entscheidungen über Ihre Datenbank einbeziehen.

102
Mr.Brownstone

PostgreSQL- und BRIN-Indizes

Testen Sie es selbst. Dies ist kein Problem auf einem 5 Jahre alten Laptop mit einer SSD.

EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
  SELECT
    x::int AS id,
    (x::int % 20000)::int AS locid,  -- fake location ids in the range of 1-20000
    now() AS tsin,                   -- static timestmap
    97.5::numeric(5,2) AS temp,      -- static temp
    x::int AS usage                  -- usage the same as id not sure what we want here.
  FROM generate_series(1,1728000000) -- for 1.7 billion rows
    AS gs(x);

                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series gs  (cost=0.00..15.00 rows=1000 width=4) (actual time=173119.796..750391.668 rows=1728000000 loops=1)
 Planning time: 0.099 ms
 Execution time: 1343954.446 ms
(3 rows)

Das Erstellen der Tabelle dauerte also 22 Minuten. Vor allem, weil der Tisch bescheidene 97 GB hat. Als nächstes erstellen wir die Indizes,

CREATE INDEX ON electrothingy USING brin (tsin);
CREATE INDEX ON electrothingy USING brin (id);    
VACUUM ANALYZE electrothingy;

Es hat lange gedauert, auch die Indizes zu erstellen. Obwohl sie BRIN sind, sind sie nur 2-3 MB groß und können problemlos im RAM gespeichert werden. Das Lesen von 96 GB ist nicht sofort möglich, aber für Ihren Laptop bei Ihrer Arbeitsbelastung kein wirkliches Problem.

Jetzt fragen wir es ab.

explain analyze
SELECT max(temp)
FROM electrothingy
WHERE id BETWEEN 1000000 AND 1001000;
                                                                 QUERY PLAN                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=5245.22..5245.23 rows=1 width=7) (actual time=42.317..42.317 rows=1 loops=1)
   ->  Bitmap Heap Scan on electrothingy  (cost=1282.17..5242.73 rows=993 width=7) (actual time=40.619..42.158 rows=1001 loops=1)
         Recheck Cond: ((id >= 1000000) AND (id <= 1001000))
         Rows Removed by Index Recheck: 16407
         Heap Blocks: lossy=128
         ->  Bitmap Index Scan on electrothingy_id_idx  (cost=0.00..1281.93 rows=993 width=0) (actual time=39.769..39.769 rows=1280 loops=1)
               Index Cond: ((id >= 1000000) AND (id <= 1001000))
 Planning time: 0.238 ms
 Execution time: 42.373 ms
(9 rows)

Update mit Zeitstempeln

Hier generieren wir eine Tabelle mit verschiedenen Zeitstempeln, um die Anforderung zum Indizieren und Suchen in einer Zeitstempelspalte zu befriedigen. Die Erstellung dauert etwas länger, da to_timestamp(int) wesentlich langsamer ist als now() (was wird für die Transaktion zwischengespeichert)

EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
  SELECT
    x::int AS id,
    (x::int % 20000)::int AS locid,
    -- here we use to_timestamp rather than now(), we
    -- this calculates seconds since Epoch using the gs(x) as the offset
    to_timestamp(x::int) AS tsin,
    97.5::numeric(5,2) AS temp,
    x::int AS usage
  FROM generate_series(1,1728000000)
    AS gs(x);

                                                               QUERY PLAN                                                                
-----------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series gs  (cost=0.00..17.50 rows=1000 width=4) (actual time=176163.107..5891430.759 rows=1728000000 loops=1)
 Planning time: 0.607 ms
 Execution time: 7147449.908 ms
(3 rows)

Jetzt können wir stattdessen eine Abfrage für einen Zeitstempelwert ausführen.

explain analyze
SELECT count(*), min(temp), max(temp)
FROM electrothingy WHERE tsin BETWEEN '1974-01-01' AND '1974-01-02';
                                                                        QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=296073.83..296073.84 rows=1 width=7) (actual time=83.243..83.243 rows=1 loops=1)
   ->  Bitmap Heap Scan on electrothingy  (cost=2460.86..295490.76 rows=77743 width=7) (actual time=41.466..59.442 rows=86401 loops=1)
         Recheck Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
         Rows Removed by Index Recheck: 18047
         Heap Blocks: lossy=768
         ->  Bitmap Index Scan on electrothingy_tsin_idx  (cost=0.00..2441.43 rows=77743 width=0) (actual time=40.217..40.217 rows=7680 loops=1)
               Index Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
 Planning time: 0.140 ms
 Execution time: 83.321 ms
(9 rows)

Ergebnis:

 count |  min  |  max  
-------+-------+-------
 86401 | 97.50 | 97.50
(1 row)

In 83,321 ms können wir also 86.401 Datensätze in einer Tabelle mit 1,7 Milliarden Zeilen zusammenfassen. Das sollte vernünftig sein.

Stundenende

Das Berechnen des Stundenendes ist ebenfalls ziemlich einfach. Schneiden Sie die Zeitstempel ab und fügen Sie dann einfach eine Stunde hinzu.

SELECT date_trunc('hour', tsin) + '1 hour' AS tsin,
  count(*),
  min(temp),
  max(temp)
FROM electrothingy
WHERE tsin >= '1974-01-01'
  AND tsin < '1974-01-02'
GROUP BY date_trunc('hour', tsin)
ORDER BY 1;
          tsin          | count |  min  |  max  
------------------------+-------+-------+-------
 1974-01-01 01:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 02:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 03:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 04:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 05:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 06:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 07:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 08:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 09:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 10:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 11:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 12:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 13:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 14:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 15:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 16:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 17:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 18:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 19:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 20:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 21:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 22:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 23:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-02 00:00:00-06 |  3600 | 97.50 | 97.50
(24 rows)

Time: 116.695 ms

Es ist wichtig zu beachten, dass kein Index für die Aggregation verwendet wird, obwohl dies möglich ist. Wenn dies Ihre typische Abfrage ist, möchten Sie wahrscheinlich einen BRIN für date_trunc('hour', tsin). Darin liegt ein kleines Problem darin, dass date_trunc Nicht unveränderlich ist. Sie müssten ihn also zuerst umbrechen, um dies zu erreichen.

Partitionierung

Ein weiterer wichtiger Informationspunkt zu PostgreSQL ist, dass PG 10 Partitionierung von DDL bringt. So können Sie beispielsweise problemlos Partitionen für jedes Jahr erstellen. Teilen Sie Ihre bescheidene Datenbank in kleinere auf, die winzig sind. Auf diese Weise sollten Sie in der Lage sein, btree-Indizes anstelle von BRIN zu verwenden und zu verwalten, was sogar noch schneller wäre.

CREATE TABLE electrothingy_y2016 PARTITION OF electrothingy
    FOR VALUES FROM ('2016-01-01') TO ('2017-01-01');

Oder Wasauchimmer.

63
Evan Carroll

Es wundert mich, dass hier niemand das Benchmarking erwähnt hat - bis @ EvanCarroll kam mit seinem hervorragenden Beitrag!

Wenn ich Sie wäre, würde ich einige Zeit damit verbringen (und ja, ich weiß, es ist ein kostbares Gut!), Systeme einzurichten und das auszuführen, was Sie denken (erhalten Sie hier Eingaben von Endbenutzern!), Sagen wir, Ihre 10 häufigsten Abfragen.

Meine eigenen Gedanken:

NoSQL-Lösungen können für bestimmte Anwendungsfälle sehr gut funktionieren, sind jedoch für Ad-hoc-Abfragen häufig unflexibel. Eine amüsante Darstellung von NoSQL von Brian Aker - ehemaliger Chefarchitekt von MySQL - finden Sie unter hier !

Ich stimme @ Mr.Brownstone zu, dass Ihre Daten hervorragend für eine relationale Lösung geeignet sind (und diese Meinung war bestätigt von Evan Carroll )!

Wenn ich mich auf irgendwelche Ausgaben festlegen würde, wäre es meine Festplattentechnologie! Ich würde jedes Geld ausgeben, das mir zur Verfügung stand für NAS oder SAN oder vielleicht einige SSD-Festplatten, um meine selten geschriebenen aggregierten Daten zu speichern!

Zuerst würde ich mir ansehen, was ich jetzt zur Verfügung habe . Führen Sie einige Tests durch und zeigen Sie die Ergebnisse den Entscheidungsträgern. Sie haben bereits einen Proxy in Form von ECs Arbeit ! Ein oder zwei schnelle Tests auf Ihrer eigenen Hardware wären jedoch überzeugender!

Dann denken Sie darüber nach, Geld auszugeben! Wenn Sie Geld ausgeben möchten, schauen Sie sich zuerst die Hardware und nicht die Software an. AFAIK, Sie können die Festplattentechnologie für einen Testzeitraum ausleihen oder noch besser ein paar Proof-of-Concept-Tests in der Cloud durchführen.

Meine persönliche erste Anlaufstelle für ein Projekt wie dieses wäre PostgreSQL. Das heißt nicht, dass ich eine proprietäre Lösung ausschließen würde, aber die Gesetze der Physik und der Festplatten sind für alle gleich! "Yae cannae beet die Gesetze der Physik Jim" :-)

14
Vérace

Wenn Sie dies noch nicht getan haben, sehen Sie sich ein Zeitreihen-DBMS an, da es für das Speichern und Abfragen von Daten optimiert ist, bei denen der Datums-/Zeittyp im Vordergrund steht. In der Regel werden Zeitreihendatenbanken zum Aufzeichnen von Daten im Minuten-/Sekunden-/Subsekundenbereich verwendet. Daher bin ich mir nicht sicher, ob sie noch für stündliche Schritte geeignet sind. Diese Art von DBMS scheint jedoch einen Blick wert zu sein. Derzeit scheint InfluxDB die etablierteste und am weitesten verbreitete Zeitreihendatenbank zu sein.

6
FloorDivision

Natürlich ist dies kein NoSQL-Problem, aber ich würde vorschlagen, dass eine RDBMS-Lösung zwar funktionieren würde, ich denke jedoch, dass ein OLAP -Ansatz viel besser passt und angesichts der sehr begrenzten Datenbereiche würde ich dies stark tun Schlagen Sie vor, die Verwendung einer spaltenbasierten Datenbank anstelle einer zeilenbasierten Datenbank zu untersuchen. Stellen Sie sich das so vor: Sie verfügen möglicherweise über 1,7 Milliarden Daten, benötigen jedoch nur 5 Bit, um jeden möglichen Wert von Stunde oder Tag des Monats zu indizieren.

Ich habe Erfahrung mit einer ähnlichen Problemdomäne, in der Sybase IQ (jetzt SAP IQ) zum Speichern von bis zu 300 Millionen Zählern pro Stunde Leistungsmanagementdaten für Telekommunikationsgeräte verwendet wird, aber ich bezweifle, dass Sie das Budget für diese Art von Lösung haben. Im Open-Source-Bereich ist MariaDB ColumnStore ein vielversprechender Kandidat, aber ich würde empfehlen, auch MonetDB zu untersuchen.

Da die Abfrageleistung ein wichtiger Treiber für Sie ist, sollten Sie berücksichtigen, wie Abfragen formuliert werden. Hier zeigen OLAP und RDBMS ihre größten Unterschiede: - Mit OLAP normalisieren Sie die Abfrageleistung, um Wiederholungen nicht zu reduzieren, Speicher zu reduzieren oder sogar die Konsistenz zu erzwingen Zusätzlich zum ursprünglichen Zeitstempel (Sie haben hoffentlich daran gedacht, die Zeitzone zu erfassen?) Haben Sie ein separates Feld für den UTC-Zeitstempel, andere für Datum und Uhrzeit und noch mehr für Jahr, Monat, Tag, Stunde, Minuten- und UTC-Versatz. Wenn Sie zusätzliche Informationen zu Standorten haben, können Sie diese in einer separaten Standorttabelle aufbewahren, die bei Bedarf nachgeschlagen werden kann. Sie können den Schlüssel für diese Tabelle auch in Ihrem Hauptdatensatz behalten, aber den vollständigen Standortnamen beibehalten Schließlich benötigen alle möglichen Speicherorte auch in Ihrer Haupttabelle nur noch 10 Bit für die Indizierung, und jede Referenz, der Sie nicht folgen müssen, um die zu meldenden Daten zu erhalten, spart Zeit bei Ihrer Abfrage.

Verwenden Sie als letzten Vorschlag separate Tabellen für beliebte aggregierte Daten und verwenden Sie Stapeljobs, um sie zu füllen. Auf diese Weise müssen Sie die Übung nicht für jeden Bericht wiederholen, der einen aggregierten Wert verwendet und Abfragen durchführt, die aktuelle mit historischen oder historischen Daten vergleichen historisch zu historisch viel einfacher und viel, viel schneller.

4
Paul Smith