sql >> Database teknologi >  >> RDS >> PostgreSQL

Find overlappende datointervaller i PostgreSQL

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


  1. ORA-00984:kolonne ikke tilladt her

  2. Advarsel:mysql_connect():[2002] Ingen sådan fil eller mappe (forsøger at oprette forbindelse via unix:///tmp/mysql.sock) i

  3. Efter en enkelt transaktions dødvande på tværs af SQL Server-versioner

  4. Structured Query Language – Vigtigheden af ​​at lære SQL