PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Trova e somma gli intervalli di date con record sovrapposti in postgresql

demo:db<>violino (usa il vecchio set di dati con la parte A-B sovrapposta)

Disclaimer: Funziona per intervalli di giorni non per timestamp. Il requisito per ts è arrivato dopo.

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 genera tutte le date tra inizio e fine. Quindi ogni data in cui esiste un'attività ottiene una riga con lo specifico count
  2. Raggruppamento di tutte le date, aggregazione di tutte le attività esistenti e somma dei loro conteggi
  3. HAVING filtra le date in cui esiste una sola attività
  4. Poiché ci sono giorni diversi con le stesse attività, abbiamo bisogno di un solo rappresentante:Filtra tutti i duplicati con DISTINCT ON
  5. Unisci questo risultato al tavolo originale per ottenere l'inizio e la fine. (nota che "fine" è una parola riservata in Postgres, dovresti trovare meglio un altro nome di colonna!). Era più comodo perderli prima, ma è possibile ottenere questi dati all'interno della sottoquery.
  6. Raggruppa questo join per ottenere la data più anticipata e più recente di ogni intervallo.

Ecco una versione per i timestamp:

demo:db<>violino

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

L'idea principale è quella di identificare possibili fasce orarie. Quindi prendo tutti i tempi conosciuti (sia all'inizio che alla fine) e li metto in un elenco ordinato. Quindi posso prendere i primi orari di traino conosciuti (17:00 dall'inizio A e 18:00 dall'inizio B) e controllare quale intervallo è in esso. Poi lo controllo per il 2° e 3°, poi per il 3° e 4° e così via.

Nella prima fascia oraria si adatta solo A. Nella seconda da 18-19 anche B è a posto. Nello slot successivo 19-20 anche C, dalle 20 alle 20:30 A non va più, solo B e C. Il prossimo è 20:30-22 dove si inserisce solo B, infine si aggiunge 22-23 D a B e, ultimo ma non meno importante, solo D rientra in 23-23:30.

Quindi prendo questo elenco di tempi e lo unisco di nuovo alla tabella delle attività in cui gli intervalli si intersecano. Dopodiché è solo un raggruppamento per fascia oraria e riepiloga il tuo conteggio.

  1. questo mette entrambi i ts di una riga in un array i cui elementi sono espansi in una riga per elemento con unnest . Quindi ottengo tutti i tempi in una colonna che può essere semplicemente ordinata
  2. utilizzando la funzione finestra permette di prendere il valore della riga successiva in quella corrente. Quindi posso creare un intervallo di timestamp da questi due valori con tsrange
  3. Questo filtro è necessario perché l'ultima riga non ha "valore successivo". Questo crea un NULL valore interpretato da tsrange come infinito. Quindi questo creerebbe un'incredibile fascia oraria sbagliata. Quindi dobbiamo filtrare questa riga.
  4. Unisciti alle fasce orarie contro il tavolo originale. Il && l'operatore controlla se due tipi di intervallo si sovrappongono.
  5. Raggruppamento per singole fasce orarie, aggregazione dei nomi e conteggio. Filtra le fasce orarie con una sola attività utilizzando il HAVING clausola
  6. Un po' complicato ottenere i punti di partenza e di arrivo giusti. Quindi i punti di inizio sono il massimo dell'inizio dell'attività o l'inizio di una fascia oraria (che può essere ottenuta usando lower ). Per esempio. Prendi lo slot 20-20:30:iniziano le 20 ore ma né B né C hanno il loro punto di partenza lì. Simile l'ora della fine.