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