PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Finden und summieren Sie Datumsbereiche mit überlappenden Datensätzen in postgresql

demo:db<>fiddle (verwendet den alten Datensatz mit dem überlappenden A-B-Teil)

Haftungsausschluss: Dies funktioniert für Tagesintervalle, nicht für Zeitstempel. Die Anforderung für ts kam später.

SELECT
    s.acts,
    s.sum,
    MIN(a.start) as start,
    MAX(a.end) as end
FROM (
    SELECT DISTINCT ON (acts)
        array_agg(name) as acts,
        SUM(count)
    FROM
        activities, generate_series(start, "end", interval '1 day') gs
    GROUP BY gs
    HAVING cardinality(array_agg(name)) > 1
) s
JOIN activities a
ON a.name = ANY(s.acts)
GROUP BY s.acts, s.sum
  1. generate_series generiert alle Daten zwischen Start und Ende. Jedes Datum, an dem eine Aktivität existiert, erhält also eine Zeile mit der spezifischen count
  2. Gruppieren aller Daten, Aggregieren aller vorhandenen Aktivitäten und Summe ihrer Anzahl
  3. HAVING filtert die Daten heraus, an denen nur eine Aktivität vorhanden ist
  4. Weil es verschiedene Tage mit denselben Aktivitäten gibt, brauchen wir nur einen Vertreter:Filtern Sie alle Duplikate mit DISTINCT ON
  5. Fügen Sie dieses Ergebnis mit der ursprünglichen Tabelle zusammen, um den Anfang und das Ende zu erhalten. (Beachten Sie, dass "Ende" ein reserviertes Wort in Postgres ist, Sie sollten besser einen anderen Spaltennamen finden!). Früher war es bequemer, sie zu verlieren, aber es ist möglich, diese Daten innerhalb der Unterabfrage zu erhalten.
  6. Gruppieren Sie diesen Join, um das früheste und späteste Datum jedes Intervalls zu erhalten.

Hier ist eine Version für Zeitstempel:

demo:db<>fiddle

WITH timeslots AS (
    SELECT * FROM (
        SELECT
            tsrange(timepoint, lead(timepoint) OVER (ORDER BY timepoint)),
            lead(timepoint) OVER (ORDER BY timepoint)     -- 2
        FROM (
            SELECT 
                unnest(ARRAY[start, "end"]) as timepoint  -- 1 
            FROM
                activities
            ORDER BY timepoint
        ) s
    )s  WHERE lead IS NOT NULL                            -- 3
)
SELECT 
    GREATEST(MAX(start), lower(tsrange)),                 -- 6
    LEAST(MIN("end"), upper(tsrange)),
    array_agg(name),                                      -- 5
    sum(count)
FROM 
    timeslots t
JOIN activities a
ON t.tsrange && tsrange(a.start, a.end)                   -- 4
GROUP BY tsrange
HAVING cardinality(array_agg(name)) > 1

Die Hauptidee besteht darin, mögliche Zeitfenster zu identifizieren. Also nehme ich jede bekannte Zeit (sowohl Start als auch Ende) und füge sie in eine sortierte Liste ein. So kann ich die ersten bekannten Schleppzeiten (17:00 von Start A und 18:00 von Start B) nehmen und prüfen, welches Intervall drin ist. Dann überprüfe ich es für den 2. und 3., dann für den 3. und 4. und so weiter.

In den ersten Zeitschlitz passt nur A. Im zweiten von 18-19 passt auch B. Im nächsten Slot 19-20 auch C, von 20 bis 20:30 passt A nicht mehr, nur noch B und C. Der nächste ist 20:30-22 wo nur B passt, schließlich kommt 22-23 D dazu B und nicht zuletzt nur D passt in 23-23:30.

Also nehme ich diese Zeitliste und verbinde sie mit der Aktivitätstabelle, wo sich die Intervalle schneiden. Danach ist es nur eine Gruppierung nach Zeitfenster und die Zusammenfassung Ihrer Zählung.

  1. dies fügt beide ts einer Zeile in ein Array ein, dessen Elemente mit unnest zu einer Zeile pro Element expandiert werden . So bekomme ich alle mal in eine Spalte die einfach bestellt werden kann
  2. unter Verwendung der führenden Fensterfunktion ermöglicht es, den Wert der nächsten Zeile in die aktuelle zu übernehmen. Also kann ich aus diesen beiden Werten mit tsrange einen Zeitstempelbereich erstellen
  3. Dieser Filter ist notwendig, da die letzte Zeile keinen "nächsten Wert" hat. Dadurch wird ein NULL erstellt Wert, der von tsrange interpretiert wird als unendlich. Das würde also ein unglaublich falsches Zeitfenster schaffen. Also müssen wir diese Zeile herausfiltern.
  4. Nehmen Sie an den Zeitfenstern gegen den ursprünglichen Tisch teil. Der && Operator prüft, ob sich zwei Bereichstypen überschneiden.
  5. Gruppierung nach einzelnen Zeitfenstern, Aggregation der Namen und der Anzahl. Filtern Sie die Zeitfenster mit nur einer Aktivität heraus, indem Sie den HAVING verwenden Klausel
  6. Ein bisschen knifflig, um die richtigen Start- und Endpunkte zu finden. Die Startpunkte sind also entweder das Maximum des Aktivitätsstarts oder der Beginn eines Zeitfensters (das mit lower abgerufen werden kann ). Z.B. Nehmen Sie den 20-20:30-Slot:Er beginnt um 20 Uhr, aber weder B noch C haben dort ihren Startpunkt. Ähnlich der Endzeit.