Berechnen übereinstimmender Datenpunkte aus Fuzzy-Zeitstempeln in Postgresql

7

Ich habe eine Tabelle mit mehreren Zeitreihen verschiedener Typen. Die Zeitstempel von Kohäsionspunkten aus verschiedenen Serien stimmen nicht genau überein (dh die Differenz kann bis zu einer Stunde betragen).

Schema

Unten ist das Schema mit zwei Beispielserien:

CREATE TABLE series (id integer, series_type integer, charttime timestamp,
                     value integer, PRIMARY KEY (id));
INSERT INTO series VALUES (1, 1, '2018-03-01 12:10:00', 40),
    (2, 1, '2018-03-01 13:25:00', 30), (3, 1, '2018-03-01 14:10:00', 50);
INSERT INTO series VALUES (4, 2, '2018-03-01 11:20:00', 2), (5, 2, '2018-03-01 12:15:00', 6),
    (6, 2, '2018-03-01 13:00:00', 7), (7, 2, '2018-03-01 13:45:00', 1);

id |series_type |charttime           |value |
---|------------|--------------------|------|
1  |1           |2018-03-01 12:10:00 |40    |
2  |1           |2018-03-01 13:25:00 |30    |
3  |1           |2018-03-01 14:10:00 |50    |
4  |2           |2018-03-01 11:20:00 |2     |
5  |2           |2018-03-01 12:15:00 |6     |
7  |2           |2018-03-01 13:45:00 |1     |
6  |2           |2018-03-01 13:00:00 |7     |

Tor

Ziel ist es, eine Serie zusammen mit dem nächstgelegenen Datenpunkt aus einer anderen Serie auszuwählen. Für den Beispieldatensatz sollte das Ergebnis sein:

charttime           |s1 |s2 |
--------------------|---|---|
2018-03-01 12:10:00 |40 |6  |
2018-03-01 13:25:00 |30 |1  |
2018-03-01 14:10:00 |50 |1  |

Erster Arbeitsansatz

Mein aktueller Ansatz besteht darin, den am besten passenden Datenpunkt aus der anderen Reihe durch eine Unterabfrage auszuwählen:

SELECT l.charttime, l.value AS s1,
    ( SELECT r.value
      FROM series r
      WHERE ABS( EXTRACT( EPOCH FROM l.charttime - r.charttime ) / 3600 ) < 1
            AND r.series_type = 2
      ORDER BY ABS( EXTRACT( EPOCH FROM l.charttime - r.charttime )) ASC LIMIT 1 
    ) AS s2
FROM series l
WHERE l.series_type = 1
ORDER BY l.charttime ASC

Dies scheint nicht der beste Ansatz zu sein, da das Dataset sehr groß ist und daher die Ausführung vieler Unterabfragen die Abfrage verlangsamt.

Zweiter Ansatz

Eine andere Idee besteht darin, die Tabelle selbst zu verknüpfen und nach Zeitstempeln für geschlossene Daten zu filtern:

SELECT l.charttime, l.value AS s1, r.charttime, r.value AS s2
FROM series l, series r
WHERE abs(EXTRACT(EPOCH FROM l.charttime - r.charttime) / 3600) < 1
      AND l.series_type = 1 AND r.series_type = 2

charttime           |s1 |charttime           |s2 |
--------------------|---|--------------------|---|
2018-03-01 12:10:00 |40 |2018-03-01 11:20:00 |2  |
2018-03-01 12:10:00 |40 |2018-03-01 12:15:00 |6  |
2018-03-01 12:10:00 |40 |2018-03-01 13:00:00 |7  |
2018-03-01 13:25:00 |30 |2018-03-01 13:45:00 |1  |
2018-03-01 13:25:00 |30 |2018-03-01 13:00:00 |7  |
2018-03-01 14:10:00 |50 |2018-03-01 13:45:00 |1  |

Das Problem sind dann die doppelten Datenpunkte. Die Gruppierung in der ersten Spalte funktioniert nicht, da die beste Übereinstimmung s2nicht ausgewählt werden kann.

Gibt es einen besseren Ansatz?

stsc
quelle
Haben Sie eine bestimmte Anzahl von Serien? Sie sagen, das Ziel ist es, eine Serie zusammen mit dem nächsten Datenpunkt aus einer anderen Serie auszuwählen, aber in Ihrem Beispiel 40,30,50 stammen alle von series_type = 1. Ich bin auch verwirrt darüber, wie Sie zwei Datensätze und drei Punkte in Ihrer gewünschten Ausgabe haben. Ist einer von beiden die gewünschte Ausgabe? Könnten Sie die gewünschte Ausgabe bei der Eingabe anzeigen / bestätigen, damit wir einen Testfall haben?
Evan Carroll
In den angezeigten Ausgabebeispielen s2stammen die Werte in der Spalte von series_type= 2.
RDFozz
@EvanCarroll Das Beispielergebnis stammt aus der Beispielserie. Es gibt drei Datenpunkte in series_type = 1. Wie bereits erwähnt, s2sind die Übereinstimmungspunkte von series_type=2.
stsc

Antworten:

4

In Ihrem zweiten Ansatz können Sie mit dem Self-Join Duplikate entfernen, indem Sie row_number():

Partition nach l.charttime, Reihenfolge nach Zeitdifferenz und Filter für row_number = 1.

Ich denke jedoch, dass die Leistung schrecklich sein wird. Aufgrund der kartesischen Verknüpfung handelt es sich um eine O-Operation (Größe (Serie 1) x Größe (Serie 2)).

Das Vorhandensein von l.charttime und r.charttime in Funktionen kann ebenfalls Probleme verursachen. Versuchen Sie das Refactoring auf (im Pseudocode)

    r.charttime < l.charttime + 3600
and r.charttime > l.charttime - 3600

.. und sehen, wie der Abfrageplan aussieht. Ich nehme an, es gibt einen Index für die Chartzeit. Ohne einen wird kein Ansatz schnell sein. In der Tat können zwei Teilindizes , einer für Serie 1 und einer für Serie 2, sogar noch besser sein.

Michael Green
quelle
Ihre Idee mit parition by works wird durchaus funktionieren, sie hat die Abfragezeit von ca. 20s auf 1s reduziert. Ich habe eine Antwort mit dem Code gepostet, mit dem ich gelandet bin.
stsc
1

Ich lese Ihre Geschäftsregel als "Holen Sie sich jede Zeile aus Serie 1 und die nächstgelegene Zeile aus Serie 2, falls vorhanden, die innerhalb von 3600 vom Wert von Serie 1 liegen muss." Ich frage mich, ob eine gute Lösung dafür kein Cursor wäre. Nun, zwei Cursor.

Der grundlegende Algorithmus besteht darin, die zwei Werte der Serie 2 zu finden, die sich über jeden Wert der Serie 1 erstrecken, dh den Wert unmittelbar davor und den Wert unmittelbar danach in zeitlicher Abfolge. Verwenden Sie dann die nächstgelegene. Es würde ungefähr so ​​aussehen:

declare two variables: Smaller(datetime, value) and Larger(datetime, value).
initialize the variables to their domain minimum value e.g. (1900-01-01 00:00:00, 0).

open a cursor on series 1, in time order
open a cursor on series 2, in time order

while rows remain in Series1

    while Larger.datetime < Series1.datetime
        Read next Series2
        set Smaller = Larger
        set Larger = Series2
        // Add logic for when Series2 is exhausted
    end

    // We know Smaller is less than Series1.datetime and Larger is greater than or equal,
    // or there's a case not documented in the question.
    // Check for Smaller, Larger within the 3600 window to be added.
    if (Series1.datetime - Smaller.datetime) < (Larger.datetime - Series1.datetime)
        use Smaller.value
    else
        use Larger.value
    end

    read next Series1
end

Ich habe offensichtlich viele Feinheiten weggelassen. Es wird einige Fälle geben, in denen mehrere Werte der Serie 1 vor dem ersten Wert der Serie 2 oder nach dem letzten Wert der Serie 2 vorliegen. Auch wenn es in Serie 2 keine Übereinstimmung für einen bestimmten Wert in Serie 1 gibt. In Ihrer Beschreibung werden die Regeln für diese nicht erwähnt, aber ich bin sicher, dass Sie sie einarbeiten können.

Dies erfordert, dass beide Serienwerte zeitlich aufeinander abgestimmt sind. Die Sorte könnte teuer sein. Es müsste jedoch einen Index für diese Spalte geben, damit eine Lösung funktioniert. Die Abfrage hätte also bereits einen zeitlich geordneten Zugriffspfad zu den Daten, und wahrscheinlich würde es zur Laufzeit keine tatsächliche Sortierung geben.

Die zeitliche Komplexität hierfür ist O (Größe (Serie 1) + Größe (Serie 2)), dh O (N) anstelle von O (N ^ 2) der Selbstkreuzverbindung.

Michael Green
quelle
1

Basierend auf der Idee von Michael Green kam ich zu folgendem Ergebnis:

WITH c AS (
SELECT
    l.charttime,
    l.value AS s1,
    r.value AS s2,
    rank() OVER (PARTITION BY l.charttime ORDER BY abs(EXTRACT(EPOCH FROM l.charttime - r.charttime) / 3600) ASC) AS rnk
FROM
    series l, series r
WHERE
    abs(EXTRACT(EPOCH FROM l.charttime - r.charttime) / 3600) < 1
    AND l.series_type = 1
    AND r.series_type = 2
)
SELECT charttime, s1, s2 FROM c WHERE rnk = 1 ORDER BY charttime

Die Abfragezeit beträgt ungefähr 1s im Vergleich zu 20s in meinem ursprünglichen Ansatz.

stsc
quelle