La risposta attualmente accettata non risponde alla domanda. Ed è sbagliato in linea di principio. a BETWEEN x AND y
si traduce in:
a >= x AND a <= y
Compreso il limite superiore, mentre le persone in genere hanno bisogno di escludere esso:
a >= x AND a < y
Con date puoi facilmente regolare. Per l'anno 2009 usa '2009-12-31' come limite superiore.
Ma non è così semplice con timestamp che consentono cifre frazionarie. Le versioni moderne di Postgres utilizzano internamente un numero intero di 8 byte per memorizzare fino a 6 secondi frazionari (risoluzione µs). Sapendo questo, potremmo ancora farlo funzionare, ma non è intuitivo e dipende da un dettaglio di implementazione. Cattiva idea.
Inoltre, a BETWEEN x AND y
non trova intervalli sovrapposti. Abbiamo bisogno di:
b >= x AND a < y
E giocatori che non hanno mai lasciato non sono ancora considerati.
Risposta corretta
Assumendo l'anno 2009
, riformulerò la domanda senza cambiarne il significato:
"Trova tutti i giocatori di una determinata squadra che si sono uniti prima del 2010 e non hanno lasciato prima del 2009."
Query di base:
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';
Ma c'è di più:
Se l'integrità referenziale viene applicata con vincoli FK, la tabella team
di per sé è solo rumore nella query e può essere rimosso.
Mentre lo stesso giocatore può lasciare e rientrare nella stessa squadra, dobbiamo anche piegare eventuali duplicati, ad esempio con DISTINCT
.
E noi potremmo Bisogna prevedere un caso particolare:i giocatori che non sono mai andati via. Supponendo che quei giocatori abbiano NULL in date_leave
.
"Si presume che un giocatore di cui non si sa che se ne sia andato giocherà ancora oggi per la squadra."
Query perfezionata:
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);
La precedenza dell'operatore è contro di noi, AND
si lega prima di OR
. Abbiamo bisogno di parentesi.
Risposta correlata con DISTINCT
ottimizzato (se i duplicati sono comuni):
- Tabella Molti a Molti - Le prestazioni sono pessime
In genere, nomi delle persone fisiche non sono univoche e viene utilizzata una chiave primaria surrogata. Ma, ovviamente, name_player
è la chiave primaria di player
. Se tutto ciò di cui hai bisogno sono i nomi dei giocatori, non abbiamo bisogno del tavolo player
nella query:
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
operatore
Il manuale:
OVERLAPS
prende automaticamente il valore precedente della coppia come inizio. Ogni periodo di tempo è considerato rappresentare il mezzo intervallo aperto start <= time < end
, a meno che start
e end
sono uguali nel qual caso rappresenta quel singolo istante di tempo.
Per prendersi cura di potenziali NULL
valori, COALESCE
sembra più semplice:
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
Tipo di intervallo con supporto per l'indice
In Postgres 9.2 o versioni successive puoi anche operare con tipi di intervallo effettivi :
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
I tipi di intervallo aggiungono un po' di sovraccarico e occupano più spazio. 2 x date
=8 byte; 1 x daterange
=14 byte su disco o 17 byte in RAM. Ma in combinazione con l'operatore di sovrapposizione &&
la query può essere supportata con un indice GiST.
Inoltre, non è necessario inserire valori NULL in casi speciali. NULL significa "intervallo aperto" in un tipo di intervallo, esattamente ciò di cui abbiamo bisogno. La definizione della tabella non deve nemmeno cambiare:possiamo creare il tipo di intervallo al volo e supportare la query con un indice di espressione corrispondente:
CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
Correlati:
- Tabella media della cronologia delle azioni