sql >> Database >  >> RDS >> PostgreSQL

Vind overlappende datumbereiken in PostgreSQL

Het momenteel geaccepteerde antwoord geeft geen antwoord op de vraag. En in principe is het fout. a BETWEEN x AND y vertaalt naar:

a >= x AND a <= y

Inclusief de bovengrens, terwijl mensen doorgaans uitsluiten het:

a >= x AND a < y

Met datums je kunt het gemakkelijk aanpassen. Gebruik voor het jaar 2009 '2009-12-31' als bovengrens.
Maar het is niet zo eenvoudig met tijdstempels die fractionele cijfers toestaan. Moderne Postgres-versies gebruiken intern een 8-byte integer om maximaal 6 fractionele seconden (µs resolutie) op te slaan. Dit wetende zouden we kunnen laat het nog steeds werken, maar dat is niet intuïtief en hangt af van een implementatiedetail. Slecht idee.

Bovendien, a BETWEEN x AND y vindt geen overlappende bereiken. We hebben nodig:

b >= x AND a < y

En spelers die nooit zijn vertrokken worden nog niet overwogen.

Juist antwoord

Uitgaande van het jaar 2009 , ik zal de vraag anders formuleren zonder de betekenis ervan te veranderen:

"Vind alle spelers van een bepaald team die zich vóór 2010 hebben aangesloten en niet zijn vertrokken vóór 2009."

Basisvraag:

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

Maar er is meer:

Als referentiële integriteit wordt afgedwongen met FK-beperkingen, wordt de tabel team zelf is slechts ruis in de zoekopdracht en kan worden verwijderd.

Hoewel dezelfde speler hetzelfde team kan verlaten en weer bij hetzelfde team kan komen, moeten we ook mogelijke duplicaten folden, bijvoorbeeld met DISTINCT .

En we mogen moeten zorgen voor een speciaal geval:spelers die nooit zijn weggegaan. Ervan uitgaande dat die spelers NULL hebben in date_leave .

"Van een speler waarvan niet bekend is dat hij die heeft verlaten, wordt aangenomen dat hij tot op de dag van vandaag voor het team speelt."

Verfijnde zoekopdracht:

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

Operatorprioriteit werkt tegen ons, AND bindt vóór OR . We hebben haakjes nodig.

Gerelateerd antwoord met geoptimaliseerde DISTINCT (als duplicaten veel voorkomen):

  • Veel tot veel tabel - Prestaties zijn slecht

Typisch, namen van natuurlijke personen zijn niet uniek en er wordt een surrogaat-primaire sleutel gebruikt. Maar, uiteraard, name_player is de primaire sleutel van player . Als je alleen spelersnamen nodig hebt, hebben we de tafel player niet nodig in de zoekopdracht, ofwel:

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 telefoniste

De handleiding:

OVERLAPS neemt automatisch de eerdere waarde van het paar als start. Elke tijdsperiode wordt beschouwd als de half-openinterval start <= time < end , tenzij start en end zijn gelijk, in welk geval het dat ene moment vertegenwoordigt.

Om te zorgen voor mogelijke NULL waarden, COALESCE lijkt het gemakkelijkst:

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

Bereiktype met indexondersteuning

In Postgres 9.2 of hoger u kunt ook werken met werkelijke bereiktypen :

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

Bereiktypen voegen wat overhead toe en nemen meer ruimte in beslag. 2 x date =8 bytes; 1 x daterange =14 bytes op schijf of 17 bytes in RAM. Maar in combinatie met de overlap-operator && de query kan worden ondersteund met een GiST-index.

Het is ook niet nodig om NULL-waarden in speciale letters te gebruiken. NULL betekent "open bereik" in een bereiktype - precies wat we nodig hebben. De tabeldefinitie hoeft niet eens te veranderen:we kunnen het bereiktype on-the-fly maken - en de query ondersteunen met een overeenkomende expressie-index:

CREATE INDEX mv_stock_dr_idx ON mv_stock USING gist (daterange(date_join, date_leave));

Gerelateerd:

  • Tabel met gemiddelde voorraadhistorie


  1. Detecteren of een waarde ten minste één nummer bevat in SQL Server

  2. Android Studio 3.0 kanarie 1:SQL-syntaxisfout

  3. MySQL onbekende kolom in ON-clausule

  4. Houd je niet van database-triggers? Je weet gewoon niet hoe je met ze moet werken!