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
generate_series
genererer alle datoer mellem start og slut. Så hver dato, en aktivitet eksisterer, får én række med den specifikkecount
- Grupper alle datoer, aggregering af alle eksisterende aktiviteter og summen af deres antal
HAVING
frafiltrerer de datoer, hvor der kun findes én aktivitet- Fordi der er forskellige dage med de samme aktiviteter, har vi kun brug for én repræsentant:Filtrer alle dubletter med
DISTINCT ON
- 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.
- Gruppér denne deltagelse for at få den tidligste og seneste dato for hvert interval.
Her er en version til tidsstempler:
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.
- 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 - 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
- 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 aftsrange
som uendelighed. Så dette ville skabe et utroligt forkert tidspunkt. Så vi skal filtrere denne række fra. - Slå sammen med tidsvinduerne mod det originale bord.
&&
operatør kontrollerer, om to områdetyper overlapper hinanden. - Grupper efter enkelte tidsintervaller, aggregering af navne og antal. Filtrer tidsintervallerne fra med kun én aktivitet ved at bruge
HAVING
klausul - 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.