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

Finden Sie sich überschneidende Datumsbereiche in PostgreSQL

Die derzeit akzeptierte Antwort beantwortet die Frage nicht. Und das ist prinzipiell falsch. a BETWEEN x AND y übersetzt zu:

a >= x AND a <= y

Einschließlich die Obergrenze, während Menschen normalerweise ausschließen müssen es:

a >= x AND a < y

Mit Datum kannst du ganz einfach anpassen. Verwenden Sie für das Jahr 2009 '2009-12-31' als Obergrenze.
Aber mit Zeitstempeln ist es nicht so einfach die Nachkommastellen zulassen. Moderne Postgres-Versionen verwenden intern eine 8-Byte-Ganzzahl, um bis zu 6 Sekundenbruchteile (µs-Auflösung) zu speichern. Mit diesem Wissen könnten wir immer noch funktionieren, aber das ist nicht intuitiv und hängt von einem Implementierungsdetail ab. Schlechte Idee.

Außerdem a BETWEEN x AND y findet keine überlappenden Bereiche. Wir brauchen:

b >= x AND a < y

Und Spieler, die nie gegangen sind werden noch nicht berücksichtigt.

Richtige Antwort

Angenommen das Jahr 2009 , werde ich die Frage umformulieren, ohne ihre Bedeutung zu ändern:

"Finde alle Spieler eines bestimmten Teams, die vor 2010 beigetreten sind und nicht vor 2009 gegangen sind."

Grundlegende Abfrage:

SELECT p.*
FROM   team     t
JOIN   contract c USING (name_team) 
JOIN   player   p USING (name_player) 
WHERE  t.name_team = ? 
AND    c.date_join  <  date '2010-01-01'
AND    c.date_leave >= date '2009-01-01';

Aber es gibt noch mehr:

Wenn die referenzielle Integrität mit FK-Einschränkungen erzwungen wird, wird die Tabelle team selbst ist nur Rauschen in der Abfrage und kann entfernt werden.

Während derselbe Spieler dasselbe Team verlassen und ihm wieder beitreten kann, müssen wir auch mögliche Duplikate folden, zum Beispiel mit DISTINCT .

Und wir können müssen für einen Sonderfall sorgen:Spieler, die nie gegangen sind. Angenommen, diese Spieler haben NULL in date_leave .

"Es wird angenommen, dass ein Spieler, von dem nicht bekannt ist, dass er ihn verlassen hat, bis heute für das Team spielt."

Verfeinerte Abfrage:

SELECT DISTINCT p.* 
FROM   contract c
JOIN   player   p USING (name_player) 
WHERE  c.name_team = ? 
AND    c.date_join  <  date '2010-01-01'
AND   (c.date_leave >= date '2009-01-01' OR c.date_leave IS NULL);

Die Operatorpriorität arbeitet gegen uns, AND bindet vor OR . Wir brauchen Klammern.

Verwandte Antwort mit optimiertem DISTINCT (falls Duplikate häufig vorkommen):

  • Many-to-Many-Tabelle - Leistung ist schlecht

Typischerweise Namen von natürlichen Personen sind nicht eindeutig und es wird ein Ersatz-Primärschlüssel verwendet. Aber natürlich name_player ist der Primärschlüssel von player . Wenn Sie nur Spielernamen brauchen, brauchen wir die Tabelle player nicht in der Abfrage entweder:

SELECT DISTINCT name_player 
FROM   contract
WHERE  name_team = ? 
AND    date_join  <  date '2010-01-01'
AND   (date_leave >= date '2009-01-01' OR date_leave IS NULL);

SQL-OVERLAPS Betreiber

Das Handbuch:

OVERLAPS nimmt automatisch den früheren Wert des Paares als Start. Jeder Zeitraum wird als Repräsentation des halboffenen Intervalls start <= time < end angesehen , es sei denn start und end gleich sind, in welchem ​​Fall es diesen einzelnen Zeitpunkt darstellt.

Um sich um potenzielle NULL zu kümmern Werte, COALESCE scheint am einfachsten:

SELECT DISTINCT name_player 
FROM   contract
WHERE  name_team = ? 
AND    (date_join, COALESCE(date_leave, CURRENT_DATE)) OVERLAPS
       (date '2009-01-01', date '2010-01-01');  -- upper bound excluded

Bereichstyp mit Indexunterstützung

In Postgres 9.2 oder höher Sie können auch mit tatsächlichen Bereichstypen arbeiten :

SELECT DISTINCT name_player 
FROM   contract
WHERE  name_team = ? 
AND    daterange(date_join, date_leave) &&
       daterange '[2009-01-01,2010-01-01)';  -- upper bound excluded

Range-Typen fügen Overhead hinzu und nehmen mehr Platz ein. 2 x date =8 Byte; 1 x daterange =14 Bytes auf Platte oder 17 Bytes im RAM. Aber in Kombination mit dem Überlappungsoperator && die Abfrage kann mit einem GiST-Index unterstützt werden.

Außerdem müssen NULL-Werte nicht in Sonderfällen behandelt werden. NULL bedeutet "offener Bereich" in einem Bereichstyp - genau das, was wir brauchen. Die Tabellendefinition muss nicht einmal geändert werden:Wir können den Bereichstyp spontan erstellen - und die Abfrage mit einem passenden Ausdrucksindex unterstützen:

CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));

Verwandte:

  • Tabelle der durchschnittlichen Lagerhistorie