sql >> Databasteknik >  >> RDS >> PostgreSQL

Hitta överlappande datumintervall i PostgreSQL

Det för närvarande accepterade svaret besvarar inte frågan. Och det är fel i princip. a BETWEEN x AND y översätts till:

a >= x AND a <= y

Inklusive den övre gränsen, medan människor vanligtvis behöver utesluta det:

a >= x AND a < y

Med datum du kan enkelt justera. För år 2009 använd "2009-12-31" som övre gräns.
Men det är inte lika enkelt med tidsstämplar som tillåter bråktal. Moderna Postgres-versioner använder ett 8-byte heltal internt för att lagra upp till 6 bråkdelar (µs upplösning). Genom att veta detta kunde vi får det fortfarande att fungera, men det är inte intuitivt och beror på en implementeringsdetalj. Dålig idé.

Dessutom a BETWEEN x AND y hittar inte överlappande intervall. Vi behöver:

b >= x AND a < y

Och spelare som aldrig lämnat övervägs inte ännu.

Riktigt svar

Antag år 2009 , jag ska formulera om frågan utan att ändra dess innebörd:

"Hitta alla spelare i ett visst lag som gick med före 2010 och inte lämnade före 2009."

Grundläggande fråga:

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';

Men det finns mer:

Om referensintegritet upprätthålls med FK-begränsningar, visas tabellen team i sig är bara brus i frågan och kan tas bort.

Samtidigt som samma spelare kan lämna och gå med i samma lag igen, måste vi också vika eventuella dubbletter, till exempel med DISTINCT .

Och vi kan måste sörja för ett speciellt fall:spelare som aldrig lämnat. Förutsatt att dessa spelare har NULL i date_leave .

"En spelare som inte är känd för att ha lämnat antas spela för laget till denna dag."

Förfinad fråga:

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);

Operatörsföreträde motverkar oss, AND binder före OR . Vi behöver parenteser.

Relaterat svar med optimerad DISTINCT (om dubbletter är vanliga):

  • Många till många tabeller – prestanda är dåliga

Vanligtvis namn av fysiska personer är inte unika och en surrogat primärnyckel används. Men uppenbarligen, name_player är den primära nyckeln för player . Om allt du behöver är spelarnamn behöver vi inte tabellen player i frågan, antingen:

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 operatör

Manualen:

OVERLAPS tar automatiskt det tidigare värdet av paret som start. Varje tidsperiod anses representera halvöppet intervall start <= time < end , om inte start och end är lika i vilket fall det representerar det enstaka tidsögonblicket.

För att ta hand om potentiella NULL värden, COALESCE verkar enklast:

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

Räckviddstyp med indexstöd

I Postgres 9.2 eller senare du kan också arbeta med faktiska intervalltyper :

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

Räckviddstyper lägger till lite overhead och tar upp mer utrymme. 2 x date =8 byte; 1 x daterange =14 byte på disk eller 17 byte i RAM. Men i kombination med överlappningsoperatorn && frågan kan stödjas med ett GiST-index.

Du behöver inte heller använda specialfall för NULL-värden. NULL betyder "öppet intervall" i en intervalltyp - precis vad vi behöver. Tabelldefinitionen behöver inte ens ändras:vi kan skapa intervalltypen i farten - och stödja frågan med ett matchande uttrycksindex:

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

Relaterat:

  • Genomsnittlig lagerhistoriktabell


  1. Introduktion till Amazon Web Services (AWS) automatisk skalning

  2. Matcha en fras som slutar på ett prefix med fulltextsökning

  3. Använda aggregerade funktioner (SUM, AVG, MAX, MIN, COUNT, DISTINCT) i MySQL

  4. Icke försumbar skillnad i exekveringsplan med Oracle vid användning av jdbc Timestamp eller Date