Schnellste Möglichkeit, überlappende Datumsbereiche zu teilen

8

Ich habe Datumsbereichsdaten in der SQL DB-Tabelle, die diese drei (nur relevanten) Spalten enthält:

  • ID (int Identität)
  • RangeFrom (nur Datum)
  • RangeTo (nur Datum)

Für jeden beliebigen Datumsbereich kann eine beliebige Anzahl von Datensätzen vorhanden sein, die sich (vollständig oder teilweise) überlappen können.

Bedingungen

  1. Jeder Datensatz mit einem höheren ID (neuerer Datensatz) hat Vorrang vor älteren Datensätzen, die sich (vollständig oder teilweise) überlappen können
  2. Bereiche sind mindestens 1 Tag lang ( RangeFrom und RangeTo unterscheiden sich um einen Tag)

Also für einen bestimmten Zeitraum (nicht länger als zB 5 Jahre) muss ich

  1. ruft alle Bereichsaufzeichnungen ab, die in diesen Bereich fallen (entweder vollständig oder teilweise)
  2. Teilen Sie diese Überlappungen in nicht überlappende Bereiche auf
  3. gibt diese neuen nicht überlappenden Bereiche zurück

Ich nehme es an

Da es sehr viele komplexe Daten im Zusammenhang mit diesen Bereichen gibt (viele Joins usw.) und da Prozessor + Speicherleistung viel effizienter ist als SQL-DB-Engine, habe ich mich entschieden, überlappende Daten aus der Datenbank in meine Datenschicht zu laden der Bereich Chopping / Splitting im Speicher. Das gibt mir viel mehr Flexibilität und Geschwindigkeit bei Entwicklung und Ausführung.

Wenn Sie denken, dass dies in DB besser gehandhabt werden sollte, lassen Sie es mich wissen.

Frage

Ich möchte den schnellsten und möglichst auch ressourcenfressenden Konvertierungsalgorithmus schreiben. Da ich viele dieser Datensätze erhalte und sie mit verschiedenen Benutzern verbunden sind, muss ich diesen Algorithmus für jeden Benutzer und seine Menge von überlappenden Bereichsdaten ausführen.

Was wäre der effizienteste (schnelle und nicht ressourcenhungrige) Weg, diese überlappenden Bereiche aufzuteilen?

Beispieldaten

Ich habe Datensätze ID=1 bis ID=5 , die sich auf diese Weise visuell überschneiden (Daten sind eigentlich irrelevant, ich kann diese Überlappungen besser so darstellen):

%Vor%

Ergebnis sollte wie folgt aussehen:

%Vor%

Das Ergebnis sieht tatsächlich so aus, als würden wir diese Überlappungen von oben betrachten und dann IDs erhalten, die wir von dieser Top-Down-Ansicht aus sehen.

Das Ergebnis wird tatsächlich in neue Bereichsdatensätze umgewandelt, so dass alte IDs irrelevant werden. Aber ihre RangeFrom und RangeTo Werte (zusammen mit allen zugehörigen Daten) werden verwendet:

%Vor%

Dies ist natürlich nur ein Beispiel für überlappende Bereiche. Es kann alles von 0 bis X für einen bestimmten Zeitraum reichen. Und wie wir sehen können, wurde die Bereichs-ID = 2 vollständig von 4 und 6 überschrieben, so dass sie vollkommen obsolet wurde.

    
Robert Koritnik 19.04.2011, 06:31
quelle

4 Antworten

3

Wie wäre es mit einem Array von nullbaren Ganzzahlen

?

Ich habe eine eigene Idee:

  1. Für den angegebenen Datumsbereich würde ich ein In-Memory-Array mit Ganzzahlen mit so vielen Elementen erstellen, wie es Tage im Bereich gibt.

  2. Füllen Sie das Array mit null -Werten. Alle von ihnen.

  3. Datensätze nach ID in umgekehrter Reihenfolge bestellen

  4. Reduzieren Sie überlappende Bereiche, indem Sie über geordnete Datensätze iterieren, und gehen Sie für jedes Element folgendermaßen vor:

    1. hole Artikel
    2. berechnen Anfang und Ende Offset für Array (Tage Unterschied)
    3. setzt alle Array-Werte zwischen diesen beiden Offsets auf die Element-ID, aber nur, wenn der Wert null ist
    4. weiter mit Schritt 4.1
  5. Sie erhalten ein Array von abgeflachten Bereichen und füllen mit Datensatz-IDs

  6. erstellt einen neuen Datensatzsatz und erstellt jeden neuen Datensatz, wenn sich die ID im Array ändert. Jeder Datensatz sollte Daten verwenden, die mit der im Array

  7. festgelegten Datensatz-ID verknüpft sind
  8. Wiederholen Sie das Ganze für die nächste Person und ihre Menge überlappender Bereiche (vergessen Sie nicht, dasselbe Array wiederzuverwenden). = gehe zurück zu Schritt 2.

Und das ist es im Grunde.

Ein 10 Jahre gegebener Datumsbereich erfordert ein Array von ca. 3650 nullable Integer, die ich denke, ist ziemlich klein Speicherbedarf (jede ganze Zahl 4 Byte, aber ich weiß nicht, wie viel Platz belegt eine nullbare Ganzzahl, die eine int und bool hat aber wir nehmen an, dass 8 Bytes insgesamt um 3650 * 8 = 28.52k) und kann leicht und ziemlich schnell im Speicher manipuliert werden. Da ich keine Datumsbereiche, Splits oder Ähnliches speichere, handelt es sich kaum um Zuweisungsoperationen mit einem if, das prüft, ob der Wert bereits gesetzt ist.

Ein 10-Jahres-Datumsbereich ist ein selten übertriebenes Extrem. 75% der Datumsbereiche liegen innerhalb von 3 Monaten oder einem Vierteljahr (90 Tage * 8 Bytes = 720 Bytes) und 99% fallen in den Bereich eines ganzen Jahres (365 * 8 = 2920 Bytes = 2,85k)

Ich finde diesen Algorithmus mehr als geeignet zum Überlappen überlappter Datumsbereiche.

Um den Speicherbedarf zu halbieren, könnte ich int anstelle von int? verwenden und auf -1 anstelle von null setzen.

Eine vorzeitige Schleifenbrechungsmöglichkeit

Ich könnte auch eine Anzahl von Tagen behalten, die nicht gesetzt sind, und wenn sie 0 erreicht, kann ich die Schleife leicht unterbrechen, weil alle verbleibenden Bereiche vollständig überlappt sind, daher würden sie keine weiteren Werte im Array setzen. Das würde die Dinge sogar ein bisschen beschleunigen, wenn ich viele Streckenaufzeichnungen hätte (was eher selten sein wird).

    
Robert Koritnik 19.04.2011, 07:52
quelle
2

Die kostenlose Zeitraumbibliothek für .NET enthält das Tool TimePeriodIntersector , die verschiedene überlappende Zeitbereiche schneidet.

Der Algorithmus verwendet eine Zeitleiste und zählt alle Momente innerhalb eines Zeitbereichs auf (Start- / Endpunkte pro Moment zählen):

%Vor%

Das ID-Mapping sollte eine einfache Aufgabe sein.

    
Jani 09.06.2011 15:56
quelle
1

Ich bin mir nicht ganz sicher, wie nützlich das wäre, aber so wie ich das angehen würde ... (zuerst nicht optimiert für das einfache Verständnis ...)

  • konvertiert das Tabellen-Mapping von [ID- & gt; Bereich] zu [Datum- & gt; Liste von IDs].

(nach Datum sortieren, jedes Datum - egal, Start oder Ende, ist ein Beginn eines Zeitbereichs, der bis zum nächsten Datum reicht.) Damit Ihr Tisch wie folgt aussehen würde:

%Vor%
  • Wählen Sie das größte Element in jeder Liste
  • verketten die folgenden Datensätze mit demselben größten Wert.

Da Sie doppelte IDs in Ihrer endgültigen Datenbank haben ("1" wird in Ihrem Beispiel auf zwei Segmente aufgeteilt), scheint am Ende die Datenbank im Datum- & gt; ID-Format im Gegensatz zum ID- & gt; -Bereich zu behalten .

Jetzt für offensichtliche Optimierungen - natürlich nicht die Liste der IDs mit jedem Datensatz halten. Füllen Sie einfach eine Datums- & gt; ID-Tabelle mit Null-IDs aus, und während Sie sie mit den endgültigen Datensätzen füllen, ersetzen Sie den bisher größten gefundenen Datensatz:

  • Tabelle aller Datumsangaben anlegen, [Datum - & gt; ID]
  • für jeden Datensatz in Ihrer Originaltabelle:
    • Wählen Sie Daten im Bereich von-bis,
    • aus
    • Wenn einer der ID-Werte null oder niedriger als die aktuell geprüfte Datensatz-ID ist, geben Sie die aktuelle ID ein.
  • Dann verketten - wenn der nächste Datensatz dieselbe ID wie vorher hat, entferne next.
  • am Ende möchten Sie vielleicht ein wenig denormalisieren, ersetzen das Extrahieren von zwei aufeinanderfolgenden Datensätzen für einen Bereich mit [date - & gt; ID, Länge] oder [Datum - & gt; ID, Enddatum]

Das Hinzufügen eines neuen Datensatzes ist genau wie eine Iteration des Erstellungsvorgangs. Das Entfernen eines Datensatzes ist jedoch ziemlich schwierig.

    
SF. 19.04.2011 07:29
quelle
0

Sie möchten effektiv die Daten stapeln und das Maximum aus dem Stapel auswählen. Ich musste etwas Ähnliches implementieren und der Ansatz, den wir verwendet haben, der uns ein bisschen mehr Flexibilität gab, als Sie benötigen, war vielleicht nicht passend, dies zu tun:

Verfügen Sie über ein Objekt zum Verwalten der Datensätze und fügen Sie jedem Datensatz dieses Objekt hinzu. Wenn ein Datensatz hinzugefügt wird, erstellen Sie einen neuen Datumsbereich und verknüpfen Sie den Wert des Datensatzes mit dem Bereich. Überprüfen Sie dann, ob sich der Bereich mit einem anderen vorhandenen Bereich überlappt. Wenn es sich überschneidet, erstellen Sie einen neuen Bereich für jede Überlappung und verknüpfen Sie alle Werte für beide / alle (abhängig davon, ob Sie beim Hinzufügen der einzelnen Bereiche oder in einem einzelnen Durchgang die überlappten Bereiche mit dem neuen Bereich verwenden) . Dies kann entweder erfolgen, indem Sie die Daten hinzufügen, oder in einem einzigen Durchgang, sobald alle Daten hinzugefügt wurden.

Am Ende haben Sie ein Objekt, das eindeutige Bereiche enthält, denen jeweils eine Sammlung von Werten zugeordnet ist, ein bisschen wie Ihr Bild oben.

%Vor%

Sie können dann eine Klasse mit einer Glättungsfunktion (wahrscheinlich unter Verwendung des Strategie-Musters) versehen, die die eindeutigen Bereiche mit Sammlungen von Werten in eindeutige Bereiche mit einem einzigen Wert umwandelt, wodurch offensichtlich Bereiche verkettet werden, die mit demselben Wert enden .

Sie möchten eine Klasse, die den Maximalwert aus jedem eindeutigen Bereich auswählt, aber auch den Mindestwert auswählen, die Werte summieren, sie mitteln, zählen usw. usw. Jede dieser Optionen kann durch Übergeben ausgeführt werden eine andere Umsetzung der Strategie.

Wie gesagt, dieser Ansatz ist vielleicht weniger effizient als ein Ansatz, der nur den Maximalwert auswählt, da Sie in diesem Fall nicht alle Werte im Stack behalten müssten, aber die Implementierung war ziemlich geradlinig, wie ich mich erinnere .

    
Sam Holder 19.04.2011 06:48
quelle

Tags und Links