Ich versuche, mehrere Datumsbereiche zu kombinieren (meine Last beträgt ungefähr 500, in den meisten Fällen 10), die sich möglicherweise mit den größtmöglichen zusammenhängenden Datumsbereichen überschneiden oder nicht. Beispielsweise:
Daten:
CREATE TABLE test (
id SERIAL PRIMARY KEY NOT NULL,
range DATERANGE
);
INSERT INTO test (range) VALUES
(DATERANGE('2015-01-01', '2015-01-05')),
(DATERANGE('2015-01-01', '2015-01-03')),
(DATERANGE('2015-01-03', '2015-01-06')),
(DATERANGE('2015-01-07', '2015-01-09')),
(DATERANGE('2015-01-08', '2015-01-09')),
(DATERANGE('2015-01-12', NULL)),
(DATERANGE('2015-01-10', '2015-01-12')),
(DATERANGE('2015-01-10', '2015-01-12'));
Tabelle sieht so aus:
id | range
----+-------------------------
1 | [2015-01-01,2015-01-05)
2 | [2015-01-01,2015-01-03)
3 | [2015-01-03,2015-01-06)
4 | [2015-01-07,2015-01-09)
5 | [2015-01-08,2015-01-09)
6 | [2015-01-12,)
7 | [2015-01-10,2015-01-12)
8 | [2015-01-10,2015-01-12)
(8 rows)
Gewünschten Erfolge:
combined
--------------------------
[2015-01-01, 2015-01-06)
[2015-01-07, 2015-01-09)
[2015-01-10, )
Visuelle Darstellung:
1 | =====
2 | ===
3 | ===
4 | ==
5 | =
6 | =============>
7 | ==
8 | ==
--+---------------------------
| ====== == ===============>
postgresql
aggregate
range-types
Villiers Strauss
quelle
quelle
Antworten:
Annahmen / Erläuterungen
Es ist nicht erforderlich, zwischen einer
infinity
oberen Schranke und einer offenen oberen Schranke zu unterscheiden (upper(range) IS NULL
). (Sie können es so oder so haben, aber auf diese Weise ist es einfacher.)infinity
im Vergleich zu PostgreSQL-BereichstypenDa
date
es sich um einen diskreten Typ handelt, haben alle Bereiche Standardgrenzen[)
. Per Dokumentation:Für andere Typen (wie
tsrange
!) Würde ich dasselbe nach Möglichkeit durchsetzen:Lösung mit reinem SQL
Mit CTEs zur Klarheit:
Oder , wie bei Unterabfragen, schneller, aber nicht so einfach zu lesen:
Oder mit einer Unterabfrageebene weniger, aber umgekehrter Sortierreihenfolge:
ORDER BY range DESC NULLS LAST
(mitNULLS LAST
), um eine perfekt umgekehrte Sortierreihenfolge zu erhalten. Dies sollte billiger (einfacher herzustellen, passt perfekt zur Sortierreihenfolge des vorgeschlagenen Index) und für Eckfälle mit genau seinrank IS NULL
.Erklären
a
:range
Berechnen Sie während der Bestellung nach das laufende Maximum der oberen Schranke (enddate
) mit einer Fensterfunktion.Ersetzen Sie NULL-Grenzen (unbegrenzt) durch +/-
infinity
, um dies zu vereinfachen (keine speziellen NULL-Fälle).b
: In der gleichen Sortierreihenfolge, wenn die vorherigeenddate
früher ist alsstartdate
wir eine Lücke haben und einen neuen Bereich beginnen (step
).Denken Sie daran, dass die Obergrenze immer ausgeschlossen ist.
c
: Bilden Sie Gruppen (grp
), indem Sie Schritte mit einer anderen Fensterfunktion zählen.Im äußeren
SELECT
Build reicht die Unter- bis Obergrenze in jeder Gruppe. Voilá.Eng verwandte Antwort auf SO mit mehr Erklärung:
Verfahrenslösung mit plpgsql
Funktioniert für jeden Tabellen- / Spaltennamen, jedoch nur für Typ
daterange
.Prozedurale Lösungen mit Schleifen sind normalerweise langsamer, aber in diesem speziellen Fall erwarte ich, dass die Funktion wesentlich schneller ist, da sie nur einen einzigen sequentiellen Scan benötigt :
Anruf:
Die Logik ist ähnlich wie bei den SQL-Lösungen, aber wir können mit einem einzigen Durchgang auskommen.
SQL-Geige.
Verbunden:
Der übliche Drill zum Behandeln von Benutzereingaben in dynamischem SQL:
Index
Für jede dieser Lösungen wäre ein einfacher (Standard-) Btree-Index
range
für die Leistung in großen Tabellen von entscheidender Bedeutung:Ein Btree-Index ist für Bereichstypen von begrenztem Nutzen , aber wir können vorsortierte Daten und möglicherweise sogar einen Nur-Index-Scan abrufen.
quelle
EXPLAIN ( ANALYZE, TIMING OFF)
und vergleichen Sie das Beste aus fünf.max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
ist. Kann es nicht einfach seinCOALESCE(upper(range), 'infinity') as enddate
? AFAIKmax() + over (order by range)
wird gleichupper(range)
hierher zurückkehren.Das habe ich mir ausgedacht:
Muss noch etwas geschliffen werden, aber die Idee ist folgende:
+
) fehlschlägt, geben Sie den bereits erstellten Bereich zurück und initialisieren Sie ihn neuquelle
generate_series()
für jede Reihe zu laufen , besonders wenn es offene Bereiche geben kann ...Vor einigen Jahren habe ich verschiedene Lösungen (unter anderem ähnliche Lösungen wie bei @ErwinBrandstetter) zum Zusammenführen überlappender Zeiträume auf einem Teradata-System getestet und die folgende als die effizienteste herausgefunden (unter Verwendung von Analytical Functions, neuere Version von Teradata haben integrierte Funktionen für diese Aufgabe).
maxEnddate
maxEnddate
mitLEAD
und Sie sind fast fertig. Nur für die letzte ZeileLEAD
gibt a zurückNULL
, um dies zu lösen, berechnen Sie das maximale Enddatum aller Zeilen einer Partition in Schritt 2 undCOALESCE
es.Warum war es schneller? Abhängig von den tatsächlichen Daten kann Schritt 2 die Anzahl der Zeilen erheblich reduzieren, sodass der nächste Schritt nur eine kleine Teilmenge verarbeiten muss und zusätzlich die Aggregation entfernt.
Geige
Da dies bei Teradata am schnellsten war, weiß ich nicht, ob es bei PostgreSQL dasselbe ist. Es wäre schön, wenn Sie einige tatsächliche Leistungszahlen erhalten würden.
quelle
Zum Spaß habe ich es ausprobiert. Ich fand dies die schnellste und sauberste Methode, um dies zu tun. Zuerst definieren wir eine Funktion, die zusammengeführt wird, wenn es eine Überlappung gibt oder wenn die beiden Eingänge benachbart sind, wenn es keine Überlappung oder Nachbarschaft gibt, geben wir einfach den ersten Datumsbereich zurück. Hinweis
+
ist eine Bereichsvereinigung im Kontext von Bereichen.Dann verwenden wir es so,
quelle
('2015-01-01', '2015-01-03'), ('2015-01-03', '2015-01-05'), ('2015-01-05', '2015-01-06')
.