Ich habe eine Situation, die meiner Meinung nach mit der Fensterfunktion gelöst werden kann, bin mir aber nicht sicher.
Stellen Sie sich die folgende Tabelle vor
CREATE TABLE tmp
( date timestamp,
id_type integer
) ;
INSERT INTO tmp
( date, id_type )
VALUES
( '2017-01-10 07:19:21.0', 3 ),
( '2017-01-10 07:19:22.0', 3 ),
( '2017-01-10 07:19:23.1', 3 ),
( '2017-01-10 07:19:24.1', 3 ),
( '2017-01-10 07:19:25.0', 3 ),
( '2017-01-10 07:19:26.0', 5 ),
( '2017-01-10 07:19:27.1', 3 ),
( '2017-01-10 07:19:28.0', 5 ),
( '2017-01-10 07:19:29.0', 5 ),
( '2017-01-10 07:19:30.1', 3 ),
( '2017-01-10 07:19:31.0', 5 ),
( '2017-01-10 07:19:32.0', 3 ),
( '2017-01-10 07:19:33.1', 5 ),
( '2017-01-10 07:19:35.0', 5 ),
( '2017-01-10 07:19:36.1', 5 ),
( '2017-01-10 07:19:37.1', 5 )
;
Ich möchte bei jeder Änderung in der Spalte id_type eine neue Gruppe haben. ZB 1. Gruppe von 7:19:21 bis 7:19:25 Uhr, 2. Start und Ziel um 7:19:26 Uhr und so weiter.
Nachdem es funktioniert, möchte ich weitere Kriterien einschließen, um Gruppen zu definieren.
In diesem Moment mit der Abfrage unten ...
SELECT distinct
min(min(date)) over w as begin,
max(max(date)) over w as end,
id_type
from tmp
GROUP BY id_type
WINDOW w as (PARTITION BY id_type)
order by begin;
Ich erhalte folgendes Ergebnis:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:37.1 5
Während ich möchte:
begin end id_type
2017-01-10 07:19:21.0 2017-01-10 07:19:25.0 3
2017-01-10 07:19:26.0 2017-01-10 07:19:26.0 5
2017-01-10 07:19:27.1 2017-01-10 07:19:27.1 3
2017-01-10 07:19:28.0 2017-01-10 07:19:29.0 5
2017-01-10 07:19:30.1 2017-01-10 07:19:30.1 3
2017-01-10 07:19:31.0 2017-01-10 07:19:31.0 5
2017-01-10 07:19:32.0 2017-01-10 07:19:32.0 3
2017-01-10 07:19:33.1 2017-01-10 07:19:37.1 5
Nachdem ich diesen ersten Schritt gelöst habe, füge ich weitere Spalten hinzu, die als Regeln zum Aufteilen von Gruppen verwendet werden sollen. Diese anderen Spalten können nicht mehr verwendet werden.
Postgres-Version: 8.4 (Wir haben Postgres mit Postgis, daher ist ein Upgrade nicht einfach. Die Postgis-Funktionen ändern ihre Namen und es gibt andere Probleme. Wir hoffen jedoch, dass wir bereits alles neu schreiben und die neue Version eine neuere Version 9.X mit verwenden wird postgis 2.x)
Antworten:
Für ein paar Punkte,
tmp
, die nur verwirrend wird..0
)date
. Wenn es Datum und Uhrzeit hat, ist es ein Zeitstempel (und speichern Sie es als einen)Besser eine Fensterfunktion verwenden ..
Ausgänge
Erklärung
Zuerst brauchen wir Resets. Wir generieren sie mit
lag()
Dann zählen wir, um Gruppen zu bekommen.
Dann wickeln wir in einem subselect
GROUP BY
undORDER
und wählen Sie den min max (Bereich)quelle
1. Fensterfunktionen plus Unterabfragen
Zählen Sie die Schritte, um Gruppen zu bilden, ähnlich wie bei Evans Idee , mit Änderungen und Korrekturen:
Dies setzt voraus, dass es sich um betroffene Spalten handelt
NOT NULL
. Sonst müssen Sie mehr tun.Auch vorausgesetzt
date
, definiert zu seinUNIQUE
, sonst müssen Sie einen Tiebreaker zu denORDER BY
Klauseln hinzufügen , um deterministische Ergebnisse zu erhalten. Wie:ORDER BY date, id
.Detaillierte Erklärung (Antwort auf sehr ähnliche Frage):
Beachten Sie insbesondere:
In verwandten Fällen kann es
lag()
mit 3 Parametern wesentlich sein, den Eckfall der ersten (oder letzten) Reihe elegant abzudecken. (Der 3. Parameter wird standardmäßig verwendet, wenn keine vorherige (nächste) Zeile vorhanden ist.Da wir nur an einer tatsächlichen Änderung von
id_type
(TRUE
) interessiert sind , spielt es in diesem speziellen Fall keine Rolle.NULL
undFALSE
beide zählen nicht alsstep
.count(step OR NULL) OVER (ORDER BY date)
ist die kürzeste Syntax, die auch in Postgres 9.3 oder älter funktioniert.count()
zählt nur Werte ungleich Null ...In modernen Postgres wäre die sauberere, äquivalente Syntax:
Einzelheiten:
2. Subtrahieren Sie zwei Fensterfunktionen, eine Unterabfrage
Ähnlich wie Eriks Idee mit Modifikationen:
Wenn
date
definiert istUNIQUE
, wie ich oben erwähne (Sie haben es nie geklärt),dense_rank()
wäre es sinnlos, da das Ergebnis dasselbe ist wie fürrow_number()
und das letztere wesentlich billiger ist.Wenn
date
ist nicht definiertUNIQUE
(und wir wissen nicht , dass die einzigen Duplikate auf(date, id_type)
), die alle diese Fragen sind sinnlos, da das Ergebnis willkürlich ist.Außerdem ist eine Unterabfrage in der Regel billiger als ein CTE in Postgres. Verwenden Sie CTEs nur, wenn Sie sie benötigen .
Verwandte Antworten mit mehr Erklärung:
In verwandten Fällen, in denen wir bereits eine laufende Nummer in der Tabelle haben, können wir mit einer einzigen Fensterfunktion auskommen:
3. Höchstleistung mit plpgsql Funktion
Da diese Frage unerwartet populär geworden ist, füge ich eine weitere Lösung hinzu, um die Spitzenleistung zu demonstrieren.
SQL verfügt über viele ausgereifte Tools, um Lösungen mit kurzer und eleganter Syntax zu erstellen. Eine deklarative Sprache hat jedoch ihre Grenzen für komplexere Anforderungen, die prozedurale Elemente beinhalten.
Eine serverseitige prozedurale Funktion ist dafür schneller als alles, was bisher veröffentlicht wurde, da sie nur einen einzigen sequentiellen Scan der Tabelle und eine einzige Sortieroperation benötigt . Wenn ein passender Index verfügbar ist, kann auch nur ein einziger Index-Scan durchgeführt werden.
Anruf:
Testen Sie mit:
Sie können die Funktion mit polymorphen Typen generisch machen und Tabellentyp und Spaltennamen übergeben. Einzelheiten:
Wenn Sie eine Funktion dafür nicht beibehalten möchten oder können, lohnt es sich sogar, eine temporäre Funktion im laufenden Betrieb zu erstellen. Kostet ein paar ms.
dbfiddle für Postgres 9.6, Leistungsvergleich für alle drei. Aufbau aufJacks Testfall, modifiziert.
dbfiddle für Postgres 8.4, wo die Leistungsunterschiede noch größer sind.
quelle
count(x or null)
oder was sie dort tun. Vielleicht könnten Sie einige Beispiele zeigen , wo es ist erforderlich, weil es hier nicht erforderlich. Und was würde das Erfordernis ausmachen, diese Eckfälle abzudecken? Übrigens habe ich meine Downvote in die Upvote geändert, nur für das Beispiel pl / pgsql. Das ist wirklich cool. (Aber im Allgemeinen bin ich gegen Antworten, die andere Antworten zusammenfassen oder Eckfälle abdecken - obwohl ich es hasse zu sagen, dass dies ein Eckfall ist, weil ich es nicht verstehe).count(x or null)
macht. Gerne stelle ich Ihnen beide Fragen, wenn Sie es vorziehen.count(x or null)
in den Lücken und Inseln benötigt?Sie können dies als einfache Subtraktion von
ROW_NUMBER()
Operationen tun (oder wenn Ihre Daten nicht eindeutig sind, obwohl sie pro immer noch eindeutig sindid_type
, können SieDENSE_RANK()
stattdessen verwenden, obwohl dies eine teurere Abfrage sein wird):Sehen Sie sich diese Arbeit bei DB Fiddle an (oder schauen Sie sich die DENSE_RANK-Version an) )
Ergebnis:
Logischerweise können Sie sich dies als ein einfaches
DENSE_RANK()
mit einem vorstellen, dhPREORDER BY
Sie möchten, dassDENSE_RANK
alle Elemente, die zusammen gereiht sind, und Sie möchten, dass sie nach Datum geordnet sind. Sie müssen sich nur mit dem lästigen Problem der Tatsache befassen, dass wird bei jeder Änderung des DatumsDENSE_RANK
erhöht. Verwenden Sie dazu den Ausdruck, den ich Ihnen oben gezeigt habe. Stellen Sie sich vor, Sie hätten diese Syntax:DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)
Dabei wird dasPREORDER
von der Rangfolgenberechnung ausgeschlossen und nur dasORDER BY
gezählt.Beachten Sie, dass dies
GROUP BY
sowohl für die generierteSeq
Spalte als auch für dieid_type
Spalte wichtig ist .Seq
ist NICHT einzigartig für sich, es kann Überlappungen geben - Sie müssen auch nach gruppierenid_type
.Weitere Informationen zu diesem Thema:
Dieser erste Link gibt Ihnen einen Code, den Sie verwenden können, wenn Sie möchten, dass das Start- oder Enddatum mit dem End- / Startdatum des vorherigen oder nächsten Zeitraums übereinstimmt (es gibt also keine Lücken). Plus andere Versionen, die Ihnen bei Ihrer Anfrage behilflich sein könnten. Sie müssen jedoch aus der SQL Server-Syntax übersetzt werden ...
quelle
In Postgres 8.4 können Sie eine RECURSIVE- Funktion verwenden.
Wie machen Sie das
Die rekursive Funktion fügt jedem unterschiedlichen id_type eine Ebene hinzu, indem die Daten nacheinander in absteigender Reihenfolge ausgewählt werden.
Verwenden Sie dann MAX (Datum), MIN (Datum), gruppiert nach Ebene, id_type, um das gewünschte Ergebnis zu erhalten.
Überprüfen Sie es: http://rextester.com/WCOYFP6623
quelle
Hier ist eine andere Methode, die Evan und Erwin insofern ähnlich ist, als sie die LAG zur Bestimmung von Inseln verwendet. Es unterscheidet sich von diesen Lösungen darin, dass es nur eine Ebene der Verschachtelung, keine Gruppierung und erheblich mehr Fensterfunktionen verwendet:
Die
is_start
berechnete Spalte in der verschachtelten SELECT-Anweisung markiert den Anfang jeder Insel. Darüber hinaus macht das verschachtelte SELECT das vorherige Datum jeder Zeile und das letzte Datum der Datenmenge verfügbar.Für Zeilen, bei denen es sich um die Anfänge der jeweiligen Inseln handelt, ist das vorherige Datum das Enddatum der vorherigen Insel. Das ist es, als was das Haupt-SELECT es verwendet. Es werden nur die Zeilen ausgewählt, die der
is_start = 1
Bedingung entsprechen, und für jede zurückgegebene Zeile werden die eigenen Zeilendate
alsbegin
und die folgenden Zeilenprev_date
als angezeigtend
. Da die letzte Zeile keine folgende Zeile enthält,LEAD(prev_date)
eine Null zurückgegeben, für die die COALESCE-Funktion das letzte Datum der Datenmenge ersetzt.Sie können mit dieser Lösung bei dbfiddle spielen .
Wenn Sie zusätzliche Spalten zur Identifizierung der Inseln einfügen, möchten Sie wahrscheinlich eine PARTITION BY-Unterklausel in die OVER-Klausel jeder Fensterfunktion einfügen. Wenn Sie beispielsweise die Inseln in Gruppen erkennen möchten, die durch a definiert sind
parent_id
, muss die obige Abfrage wahrscheinlich so aussehen:Und wenn Sie sich für die Lösung von Erwin oder Evan entscheiden, muss meiner Meinung nach auch eine ähnliche Änderung vorgenommen werden.
quelle
Mehr aus akademischem Interesse als als praktische Lösung können Sie dies auch mit einem benutzerdefinierten Aggregat erreichen . Wie die anderen Lösungen funktioniert dies auch unter Postgres 8.4, aber wie andere bereits kommentiert haben, führen Sie bitte ein Upgrade durch, wenn Sie können.
Das Aggregat wird so behandelt,
null
als ob es sich um ein anderes handeltfoo_type
, sodass Nullläufe gleich behandelt werdengrp
- das kann sein oder auch nicht, was Sie wollen.dbfiddle hier
quelle
Dies kann durchgeführt werden
RECURSIVE CTE
, um die "Startzeit" von einer Zeile zur nächsten zu verschieben, und um einige zusätzliche (bequeme) Vorbereitungen zu treffen.Diese Abfrage gibt das gewünschte Ergebnis zurück:
nach der vorbereitung ... rekursiver teil
Sie können dies unter http://rextester.com/POYM83542 überprüfen
Diese Methode lässt sich nicht gut skalieren. Für eine 8_641-Zeilentabelle werden 7 Sekunden benötigt, für eine doppelt so große Tabelle 28 Sekunden. Einige Beispiele mehr zeigen Ausführungszeiten, die wie O (n ^ 2) aussehen.
Die Methode von Evan Carrol dauert weniger als eine Sekunde (sprich: mach mit!) Und sieht aus wie O (n). Rekursive Abfragen sind absolut ineffizient und sollten als letzter Ausweg betrachtet werden.
quelle