PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Znajdź i zsumuj zakresy dat z nakładającymi się rekordami w postgresql

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
  1. 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
  2. Grupowanie wszystkich dat, agregowanie wszystkich istniejących działań i sumowanie ich liczebności
  3. HAVING odfiltrowuje daty, w których istnieje tylko jedna aktywność
  4. Ponieważ są różne dni z tymi samymi działaniami, potrzebujemy tylko jednego przedstawiciela:Filtruj wszystkie duplikaty za pomocą DISTINCT ON
  5. 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.
  6. Pogrupuj to połączenie, aby uzyskać najwcześniejszą i najpóźniejszą datę każdego interwału.

Oto wersja sygnatur czasowych:

demo:db<>skrzypce

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.

  1. 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ć
  2. 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
  3. Ten filtr jest konieczny, ponieważ ostatni wiersz nie zawiera „następnej wartości”. To tworzy NULL wartość, która jest interpretowana przez tsrange jako nieskończoność. Więc to stworzyłoby niewiarygodnie zły przedział czasowy. Musimy więc odfiltrować ten wiersz.
  4. Połącz przedziały czasowe z oryginalną tabelą. && operator sprawdza, czy dwa typy zakresów się pokrywają.
  5. Grupowanie według pojedynczych przedziałów czasowych, agregowanie nazw i liczby. Odfiltruj przedziały czasowe z tylko jedną aktywnością za pomocą HAVING klauzula
  6. 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.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Postgresql wybierz aż do osiągnięcia określonej łącznej kwoty

  2. Mnożenie dwóch kolumn, które zostały obliczone na podstawie instrukcji CASE

  3. Jak ukryć komunikaty INFO podczas uruchamiania skryptów psql?

  4. ST_DWithin przyjmuje parametr jako stopień , a nie metry , dlaczego?

  5. org.hibernate.MappingException:Brak mapowania dialektu dla typu JDBC:1111