PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Trova intervalli di date sovrapposti in PostgreSQL

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