Det aktuelt accepterede svar besvarer ikke spørgsmålet. Og det er principielt forkert. a BETWEEN x AND y
oversættes til:
a >= x AND a <= y
Inklusive den øvre grænse, mens folk typisk skal ekskludere det:
a >= x AND a < y
Med datoer du nemt kan justere. For året 2009 skal du bruge '2009-12-31' som øvre grænse.
Men det er ikke så enkelt med tidsstempler som tillader brøktal. Moderne Postgres-versioner bruger et 8-byte heltal internt til at gemme op til 6 brøksekunder (µs opløsning). Ved at vide dette kunne vi får det stadig til at fungere, men det er ikke intuitivt og afhænger af en implementeringsdetalje. Dårlig idé.
Desuden a BETWEEN x AND y
finder ikke overlappende områder. Vi har brug for:
b >= x AND a < y
Og spillere, der aldrig forlod er ikke taget i betragtning endnu.
Korrekt svar
Forudsat året 2009
, jeg omformulerer spørgsmålet uden at ændre dets betydning:
"Find alle spillere fra et givet hold, som kom med før 2010 og ikke forlod før 2009."
Grundlæggende forespørgsel:
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 der er mere:
Hvis referentiel integritet håndhæves med FK-begrænsninger, vil tabellen team
i sig selv er bare støj i forespørgslen og kan fjernes.
Mens den samme spiller kan forlade og slutte sig til det samme hold igen, skal vi også folde mulige dubletter, for eksempel med DISTINCT
.
Og vi må nødt til at sørge for et særligt tilfælde:spillere, der aldrig forlod. Forudsat at disse spillere har NULL i date_leave
.
"En spiller, der ikke vides at have forladt, antages at spille for holdet den dag i dag."
Forfinet forespørgsel:
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ørprioritet virker imod os, AND
binder før OR
. Vi har brug for parenteser.
Relateret svar med optimeret DISTINCT
(hvis dubletter er almindelige):
- Mange til mange tabel – Ydeevnen er dårlig
Typisk navne af fysiske personer er ikke unikke, og der bruges en surrogat primærnøgle. Men selvfølgelig name_player
er den primære nøgle til player
. Hvis alt du behøver er spillernavne, har vi ikke brug for bordet player
i forespørgslen, enten:
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
tager automatisk den tidligere værdi af parret som start. Hver tidsperiode anses for at repræsentere halvåben-intervallet start <= time < end
, medmindre start
og end
er ens, i hvilket tilfælde det repræsenterer det enkelte tidsøjeblik.
At tage sig af potentiel NULL
værdier, COALESCE
virker nemmest:
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
Rangetype med indeksunderstøttelse
I Postgres 9.2 eller nyere du kan også arbejde med faktiske områdetyper :
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
Rangetyper tilføjer lidt overhead og optager mere plads. 2 x date
=8 bytes; 1 x daterange
=14 bytes på disk eller 17 bytes i RAM. Men i kombination med overlapningsoperatoren &&
forespørgslen kan understøttes med et GiST-indeks.
Det er heller ikke nødvendigt at angive NULL-værdier i særlige tilfælde. NULL betyder "åben rækkevidde" i en rækkevidde - præcis hvad vi har brug for. Tabeldefinitionen behøver ikke engang at ændre sig:vi kan oprette intervaltypen med det samme - og understøtte forespørgslen med et matchende udtryksindeks:
CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));
Relateret:
- Gennemsnitlig lagerhistoriktabel