Aktuálně přijímaná odpověď na otázku neodpovídá. A v principu je to špatně. a BETWEEN x AND y
překládá do:
a >= x AND a <= y
Včetně horní mez, zatímco lidé obvykle potřebují vyloučit to:
a >= x AND a < y
S daty můžete snadno upravit. Pro rok 2009 použijte jako horní hranici '2009-12-31'.
Ale s časovými razítky to není tak jednoduché které umožňují zlomkové číslice. Moderní verze Postgresu používají interně 8bajtové celé číslo k uložení až 6 zlomkových sekund (rozlišení µs). Když to víme, mohli bychom stále to funguje, ale to není intuitivní a závisí na detailu implementace. Špatný nápad.
Navíc a BETWEEN x AND y
nenajde překrývající se rozsahy. Potřebujeme:
b >= x AND a < y
A hráči, kteří nikdy neodešli zatím nejsou zvažovány.
Správná odpověď
Za předpokladu roku 2009
, přeformuluji otázku, aniž bych změnil její význam:
"Najděte všechny hráče daného týmu, kteří se připojili před rokem 2010 a neodešli před rokem 2009."
Základní dotaz:
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';
Ale je toho víc:
Pokud je referenční integrita vynucena s omezeními FK, tabulka team
sám o sobě je pouze šum v dotazu a lze jej odstranit.
I když stejný hráč může odejít a znovu se připojit ke stejnému týmu, musíme také poskládat možné duplikáty, například pomocí DISTINCT
.
A my můžeme potřeba zajistit speciální případ:hráče, kteří nikdy neodešli. Za předpokladu, že tito hráči mají v date_leave
hodnotu NULL .
"Předpokládá se, že hráč, o kterém není známo, že odešel, hraje za tým dodnes."
Upřesněný dotaz:
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);
Přednost operátora jde proti nám, AND
se váže před OR
. Potřebujeme závorky.
Související odpověď s optimalizovaným DISTINCT
(pokud jsou duplikáty běžné):
- Tabulka mnoho k mnoha – výkon je špatný
Obvykle jména fyzických osob nejsou jedinečné a používá se náhradní primární klíč. Ale samozřejmě name_player
je primární klíč player
. Pokud potřebujete pouze jména hráčů, nepotřebujeme tabulku player
v dotazu buď:
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
operátor
Manuál:
OVERLAPS
automaticky vezme dřívější hodnotu páru jako start. Každé časové období je považováno za polovinu otevřeného intervalustart <= time < end
, pokudstart
aend
jsou stejné, v takovém případě představuje tento jediný časový okamžik.
Postarat se o potenciální NULL
hodnoty, COALESCE
zdá se nejjednodušší:
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
Typ rozsahu s podporou indexu
V Postgres 9.2 nebo novější můžete také pracovat se skutečnými typy rozsahů :
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
Typy rozsahů zvyšují režii a zabírají více místa. 2 x date
=8 bajtů; 1 x daterange
=14 bajtů na disku nebo 17 bajtů v RAM. Ale v kombinaci s operátorem překrytí &&
dotaz může být podporován indexem GiST.
Také není třeba zadávat hodnoty NULL se speciálními případy. NULL znamená "otevřený rozsah" v typu rozsahu - přesně to, co potřebujeme. Definice tabulky se ani nemusí měnit:můžeme vytvořit typ rozsahu za běhu – a podpořit dotaz s odpovídajícím indexem výrazu:
CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
Související:
- Tabulka průměrné historie akcií