demo:db<>skrzypce (używa starego zestawu danych z nakładającą się częścią A-B)
Zastrzeżenie: Działa to dla interwałów dziennych, a nie dla sygnatur czasowych. Wymóg ts pojawił się później.
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
generuje wszystkie daty od początku do końca. Tak więc każda data istnienia aktywności otrzymuje jeden wiersz z określonącount
- Grupowanie wszystkich dat, agregowanie wszystkich istniejących działań i sumowanie ich liczebności
HAVING
odfiltrowuje daty, w których istnieje tylko jedna aktywność- Ponieważ są różne dni z tymi samymi działaniami, potrzebujemy tylko jednego przedstawiciela:Filtruj wszystkie duplikaty za pomocą
DISTINCT ON
- Dołącz ten wynik do oryginalnej tabeli, aby rozpocząć i zakończyć. (zwróć uwagę, że "koniec" jest słowem zastrzeżonym w Postgresie, lepiej znajdź inną nazwę kolumny!). Wygodniej było je wcześniej stracić, ale możliwe jest uzyskanie tych danych w ramach podzapytania.
- Pogrupuj to połączenie, aby uzyskać najwcześniejszą i najpóźniejszą datę każdego interwału.
Oto wersja sygnatur czasowych:
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
Główną ideą jest określenie możliwych przedziałów czasowych. Więc biorę każdy znany czas (zarówno początek, jak i koniec) i umieszczam je na posortowanej liście. Mogę więc wziąć pierwsze znane godziny (17:00 od startu A i 18:00 od startu B) i sprawdzić, który interwał się w nim znajduje. Następnie sprawdzam 2. i 3., potem 3. i 4. i tak dalej.
W pierwszym przedziale czasowym pasuje tylko A. W drugim z lat 18-19 pasuje również B. W następnym slocie 19-20 też C, od 20 do 20:30 A już nie pasuje, tylko B i C. Kolejny to 20:30-22 gdzie pasuje tylko B, w końcu 22-23 D jest dodawane do B i ostatnie, ale nie mniej ważne, D pasują do 23-23:30.
Więc biorę tę listę czasu i dołączam ją do tabeli czynności, gdzie przecinają się interwały. Potem jest już tylko grupowanie według przedziałów czasowych i podsumowanie liczby.
- umieszcza to oba wiersze w jednej tablicy, której elementy są rozwijane do jednego wiersza na element za pomocą
unnest
. Więc dostaję wszystkie czasy do jednej kolumny, którą można po prostu zamówić - za pomocą wiodącej funkcji okna
pozwala na przejęcie wartości następnego wiersza do aktualnego. Mogę więc utworzyć zakres sygnatur czasowych z tych obu wartości za pomocą
tsrange
- Ten filtr jest konieczny, ponieważ ostatni wiersz nie zawiera „następnej wartości”. To tworzy
NULL
wartość, która jest interpretowana przeztsrange
jako nieskończoność. Więc to stworzyłoby niewiarygodnie zły przedział czasowy. Musimy więc odfiltrować ten wiersz. - Połącz przedziały czasowe z oryginalną tabelą.
&&
operator sprawdza, czy dwa typy zakresów się pokrywają. - Grupowanie według pojedynczych przedziałów czasowych, agregowanie nazw i liczby. Odfiltruj przedziały czasowe z tylko jedną aktywnością za pomocą
HAVING
klauzula - Trochę trudne, aby uzyskać właściwy punkt początkowy i końcowy. Tak więc punktami początkowymi są albo maksimum rozpoczęcia aktywności, albo początek przedziału czasowego (który można uzyskać za pomocą
lower
). Np. Weźcie przedział 20-20:30:zaczyna się 20 godzin, ale ani B, ani C nie mają tam swojego punktu początkowego. Podobny czas zakończenia.