demo:db<>fiol (använder den gamla datamängden med den överlappande A-B-delen)
Ansvarsfriskrivning: Detta fungerar för dagsintervall inte för tidsstämplar. Kravet på ts kom senare.
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
genererar alla datum mellan start och slut. Så varje datum en aktivitet existerar får en rad med det specifikacount
- Grupperar alla datum, sammanställer alla befintliga aktiviteter och summan av deras antal
HAVING
filtrerar bort de datum där det bara finns en aktivitet- Eftersom det finns olika dagar med samma aktiviteter behöver vi bara en representant:Filtrera alla dubbletter med
DISTINCT ON
- Sätt ihop detta resultat mot den ursprungliga tabellen för att få början och slutet. (observera att "slut" är ett reserverat ord i Postgres, du borde hitta ett annat kolumnnamn!). Det var bekvämare att förlora dem tidigare men det är möjligt att få dessa data i underfrågan.
- Gruppera den här medlemmen för att få det senaste och senaste datumet för varje intervall.
Här är en version för tidsstämplar:
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
Huvudtanken är att identifiera möjliga tidsluckor. Så jag tar varje känd tid (både start och slut) och lägger dem i en sorterad lista. Så jag kan ta de första kända släptiderna (17:00 från start A och 18:00 från start B) och kolla vilket intervall som finns i den. Sedan kontrollerar jag det för 2:an och 3:an, sedan för 3:an och 4:an och så vidare.
I den första tidsluckan passar bara A. I tvåan från 18-19 passar även B. I nästa lucka 19-20 även C, från 20 till 20:30 passar inte A längre, bara B och C. Nästa är 20:30-22 där bara B passar, slutligen läggs 22-23 D till B och sist men inte minst passar bara D in 23-23:30.
Så jag tar den här tidslistan och slår ihop den mot aktivitetstabellen där intervallen skär varandra. Efter det är det bara en gruppering efter tid och summera ditt antal.
- detta placerar båda ts för en rad i en array vars element expanderas till en rad per element med
unnest
. Så jag får alla tider i en kolumn som enkelt kan beställas - med hjälp av fönsterfunktionen
gör det möjligt att ta värdet på nästa rad till den nuvarande. Så jag kan skapa ett tidsstämpelintervall av dessa båda värden med
tsrange
- Detta filter är nödvändigt eftersom den sista raden inte har något "nästa värde". Detta skapar en
NULL
värde som tolkas avtsrange
som oändlighet. Så detta skulle skapa en otroligt fel tidslucka. Så vi måste filtrera bort den här raden. - Sätt ihop tidsluckorna mot det ursprungliga bordet.
&&
operatören kontrollerar om två intervalltyper överlappar varandra. - Gruppera efter enstaka tidsluckor, aggregera namnen och antalet. Filtrera bort tidsluckor med endast en aktivitet genom att använda
HAVING
klausul - Lite knepigt att få rätt start- och slutpunkter. Så startpunkterna är antingen det maximala för aktivitetsstarten eller början av en tidslucka (vilket kan fås med
lower
). T.ex. Ta luckan 20-20:30:Den börjar 20h men varken B eller C har sin startpunkt där. Liknande sluttiden.