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 intervallstart <= time < end
, om intestart
ochend
ä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