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