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

Find og summer datointervaller med overlappende poster i postgresql

demo:db<>fiddle (bruger det gamle datasæt med den overlappende A-B-del)

Ansvarsfraskrivelse: Dette virker for dagsintervaller, ikke for tidsstempler. Kravet om ts kom senere.

SELECT
    s.acts,
    s.sum,
    MIN(a.start) as start,
    MAX(a.end) as end
FROM (
    SELECT DISTINCT ON (acts)
        array_agg(name) as acts,
        SUM(count)
    FROM
        activities, generate_series(start, "end", interval '1 day') gs
    GROUP BY gs
    HAVING cardinality(array_agg(name)) > 1
) s
JOIN activities a
ON a.name = ANY(s.acts)
GROUP BY s.acts, s.sum
  1. generate_series genererer alle datoer mellem start og slut. Så hver dato, en aktivitet eksisterer, får én række med den specifikke count
  2. Grupper alle datoer, aggregering af alle eksisterende aktiviteter og summen af ​​deres antal
  3. HAVING frafiltrerer de datoer, hvor der kun findes én aktivitet
  4. Fordi der er forskellige dage med de samme aktiviteter, har vi kun brug for én repræsentant:Filtrer alle dubletter med DISTINCT ON
  5. Sæt dette resultat sammen med den originale tabel for at få starten og slutningen. (bemærk at "slut" er et reserveret ord i Postgres, du burde hellere finde et andet kolonnenavn!). Det var mere behageligt at miste dem før, men det er muligt at få disse data i underforespørgslen.
  6. Gruppér denne deltagelse for at få den tidligste og seneste dato for hvert interval.

Her er en version til tidsstempler:

demo:db<>fiddle

WITH timeslots AS (
    SELECT * FROM (
        SELECT
            tsrange(timepoint, lead(timepoint) OVER (ORDER BY timepoint)),
            lead(timepoint) OVER (ORDER BY timepoint)     -- 2
        FROM (
            SELECT 
                unnest(ARRAY[start, "end"]) as timepoint  -- 1 
            FROM
                activities
            ORDER BY timepoint
        ) s
    )s  WHERE lead IS NOT NULL                            -- 3
)
SELECT 
    GREATEST(MAX(start), lower(tsrange)),                 -- 6
    LEAST(MIN("end"), upper(tsrange)),
    array_agg(name),                                      -- 5
    sum(count)
FROM 
    timeslots t
JOIN activities a
ON t.tsrange && tsrange(a.start, a.end)                   -- 4
GROUP BY tsrange
HAVING cardinality(array_agg(name)) > 1

Hovedideen er at identificere mulige tidsintervaller. Så jeg tager alle kendte tidspunkter (både start og slut) og sætter dem på en sorteret liste. Så jeg kan tage de første slæb kendte tidspunkter (17:00 fra start A og 18:00 fra start B) og tjekke hvilket interval der er i det. Så tjekker jeg det for 2. og 3., derefter for 3. og 4. og så videre.

I det første tidsrum passer kun A. I anden fra 18-19 er også B passende. I næste spalte 19-20 også C, fra 20 til 20:30 passer A ikke længere, kun B og C. Den næste er 20:30-22, hvor kun B passer, til sidst lægges 22-23 D til B og sidst men ikke mindst passer kun D ind i 23-23:30.

Så jeg tager denne tidsliste og samler den sammen med aktivitetstabellen, hvor intervallerne krydser hinanden. Derefter er det kun en gruppering efter tidsrum og opsummer dit antal.

  1. dette sætter begge t'er i en række i én matrix, hvis elementer er udvidet til én række pr. element med unnest . Så jeg samler alle tider i én kolonne, som nemt kan bestilles
  2. ved at bruge lead-vinduefunktionen giver mulighed for at tage værdien af ​​den næste række ind i den nuværende. Så jeg kan oprette et tidsstempel ud fra disse begge værdier med tsrange
  3. Dette filter er nødvendigt, fordi den sidste række ikke har nogen "næste værdi". Dette opretter en NULL værdi, som fortolkes af tsrange som uendelighed. Så dette ville skabe et utroligt forkert tidspunkt. Så vi skal filtrere denne række fra.
  4. Slå sammen med tidsvinduerne mod det originale bord. && operatør kontrollerer, om to områdetyper overlapper hinanden.
  5. Grupper efter enkelte tidsintervaller, aggregering af navne og antal. Filtrer tidsintervallerne fra med kun én aktivitet ved at bruge HAVING klausul
  6. Det er lidt svært at få de rigtige start- og slutpunkter. Så startpunkterne er enten maksimum for aktivitetens start eller begyndelsen af ​​et tidsrum (som kan fås ved at bruge lower ). For eksempel. Tag pladsen 20-20:30:Den begynder kl. 20, men hverken B eller C har sit udgangspunkt der. Tilsvarende sluttidspunktet.


  1. forsøger at INDSÆTTE NULL, hvis inputfeltet efterlades tomt

  2. Hvordan vælger man 1d array fra 2d array?

  3. Sådan bruges DISTINCT i SQL

  4. Hvordan indstilles værdi til variabel ved hjælp af 'execute' i t-sql?