demo:db<>fiddle (usa o conjunto de dados antigo com a parte A-B sobreposta)
Isenção de responsabilidade: Isso funciona para intervalos de dias, não para carimbos de data/hora. A exigência de ts veio depois.
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
gera todas as datas entre o início e o fim. Portanto, cada data em que uma atividade existe recebe uma linha com acount
específica- Agrupando todas as datas, agregando todas as atividades existentes e somando suas contagens
HAVING
filtra as datas em que existe apenas uma atividade- Como há dias diferentes com as mesmas atividades, precisamos apenas de um representante:Filtre todas as duplicatas com
DISTINCT ON
- Junte este resultado com a tabela original para obter o início e o fim. (note que "end" é uma palavra reservada no Postgres, é melhor você encontrar outro nome de coluna!). Antes era mais confortável perdê-los, mas é possível obter esses dados na subconsulta.
- Agrupe esta junção para obter a data mais cedo e mais recente de cada intervalo.
Aqui está uma versão para carimbos de data/hora:
demo:db<>fiddle
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
A ideia principal é identificar possíveis intervalos de tempo. Então, eu pego todos os tempos conhecidos (início e fim) e os coloco em uma lista ordenada. Assim posso pegar os primeiros tempos conhecidos de reboque (17:00 da largada A e 18:00 da largada B) e verificar qual intervalo está nele. Então eu verifico para o 2º e 3º, depois para o 3º e 4º e assim por diante.
No primeiro intervalo de tempo apenas A se encaixa. No segundo de 18-19 também B é apropriado. No próximo slot 19-20 também C, das 20 às 20:30 A não cabe mais, apenas B e C. O próximo é 20:30-22 onde cabe apenas B, finalmente 22-23 D é adicionado a B e por último mas não menos importante apenas D se encaixa em 23-23:30.
Então eu pego essa lista de tempo e a junto com a tabela de atividades onde os intervalos se cruzam. Depois disso, é apenas um agrupamento por intervalo de tempo e soma sua contagem.
- isso coloca os dois ts de uma linha em uma matriz cujos elementos são expandidos em uma linha por elemento com
unnest
. Então eu coloco todos os tempos em uma coluna que pode ser simplesmente ordenada - usando a função de janela principal
permite levar o valor da próxima linha para a atual. Assim, posso criar um intervalo de carimbo de data/hora desses dois valores com
tsrange
- Este filtro é necessário porque a última linha não tem "próximo valor". Isso cria um
NULL
valor que é interpretado portsrange
como infinito. Portanto, isso criaria um incrível intervalo de tempo errado. Portanto, precisamos filtrar essa linha. - Junte os intervalos de tempo à tabela original. O
&&
operador verifica se dois tipos de intervalo se sobrepõem. - Agrupamento por intervalos de tempo únicos, agregando os nomes e a contagem. Filtre os intervalos de tempo com apenas uma atividade usando o
HAVING
cláusula - Um pouco complicado para obter os pontos iniciais e finais corretos. Portanto, os pontos de início são o máximo do início da atividade ou o início de um intervalo de tempo (que pode ser obtido usando
lower
). Por exemplo. Pegue o slot 20-20:30:Começa às 20h, mas nem B nem C tem seu ponto de partida lá. Semelhante ao horário de término.