demo:db<>fiddle (gebruikt de oude dataset met het overlappende A-B-deel)
Disclaimer: Dit werkt voor dagintervallen, niet voor tijdstempels. De vereiste voor ts kwam later.
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
genereert alle datums tussen begin en einde. Dus elke datum waarop een activiteit bestaat krijgt één rij met het specifiekecount
- Alle datums groeperen, alle bestaande activiteiten samenvoegen en optellen
HAVING
filtert de datums uit waar slechts één activiteit bestaat- Omdat er verschillende dagen zijn met dezelfde activiteiten hebben we maar één vertegenwoordiger nodig:Filter alle duplicaten met
DISTINCT ON
- Voeg dit resultaat samen met de originele tabel om het begin en einde te krijgen. (merk op dat "end" een gereserveerd woord is in Postgres, u kunt beter een andere kolomnaam zoeken!). Vroeger was het comfortabeler om ze kwijt te raken, maar het is mogelijk om deze gegevens binnen de subquery te krijgen.
- Groepeer deze deelname om de vroegste en laatste datum van elk interval te krijgen.
Hier is een versie voor tijdstempels:
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
Het belangrijkste idee is om mogelijke tijdsloten te identificeren. Dus ik neem elke bekende tijd (zowel begin als einde) en zet ze in een gesorteerde lijst. Zo kan ik de eerste bekende tijden nemen (17:00 van start A en 18:00 van start B) en kijken welke interval erin zit. Dan controleer ik het voor de 2e en 3e, dan voor de 3e een 4e enzovoort.
In het eerste tijdslot past alleen A. In de tweede van 18-19 past ook B. In het volgende slot 19-20 ook C, van 20 tot 20:30 past A niet meer, alleen B en C. De volgende is 20:30-22 waar alleen B past, tenslotte komt 22-23 D bij B en last but not least alleen D past in 23-23:30.
Dus ik neem deze tijdlijst en voeg deze samen met de activiteitentabel waar de intervallen elkaar kruisen. Daarna is het slechts een groepering op tijdslot en tel je je telling op.
- dit plaatst beide ts van een rij in één array waarvan de elementen worden uitgebreid tot één rij per element met
unnest
. Dus ik krijg alle tijden in één kolom die eenvoudig kan worden besteld - met behulp van de lead vensterfunctie
maakt het mogelijk om de waarde van de volgende rij in de huidige op te nemen. Dus ik kan een tijdstempelbereik maken van deze beide waarden met
tsrange
- Dit filter is nodig omdat de laatste rij geen "volgende waarde" heeft. Dit creëert een
NULL
waarde die wordt geïnterpreteerd doortsrange
als oneindig. Dus dit zou een ongelooflijk verkeerd tijdslot creëren. Dus we moeten deze rij eruit filteren. - Doe mee met de tijdvakken tegen de originele tafel. De
&&
operator controleert of twee bereiktypes elkaar overlappen. - Groeperen op afzonderlijke tijdvakken, de namen en het aantal bij elkaar optellen. Filter de tijdvakken met slechts één activiteit door de
HAVING
. te gebruiken clausule - Een beetje lastig om de juiste start- en eindpunten te vinden. De startpunten zijn dus ofwel het maximum van de start van de activiteit of het begin van een tijdvak (dat kan worden verkregen met
lower
). bijv. Neem het slot van 20-20:30:het begint om 20u, maar noch B noch C heeft daar zijn startpunt. Vergelijkbaar met de eindtijd.