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
generate_series
genera tutte le date tra inizio e fine. Quindi ogni data in cui esiste un'attività ottiene una riga con lo specificocount
- Raggruppamento di tutte le date, aggregazione di tutte le attività esistenti e somma dei loro conteggi
HAVING
filtra le date in cui esiste una sola attività- Poiché ci sono giorni diversi con le stesse attività, abbiamo bisogno di un solo rappresentante:Filtra tutti i duplicati con
DISTINCT ON
- 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.
- Raggruppa questo join per ottenere la data più anticipata e più recente di ogni intervallo.
Ecco una versione per i timestamp:
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.
- 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 - 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
- Questo filtro è necessario perché l'ultima riga non ha "valore successivo". Questo crea un
NULL
valore interpretato datsrange
come infinito. Quindi questo creerebbe un'incredibile fascia oraria sbagliata. Quindi dobbiamo filtrare questa riga. - Unisciti alle fasce orarie contro il tavolo originale. Il
&&
l'operatore controlla se due tipi di intervallo si sovrappongono. - Raggruppamento per singole fasce orarie, aggregazione dei nomi e conteggio. Filtra le fasce orarie con una sola attività utilizzando il
HAVING
clausola - 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.