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_seriesgera todas as datas entre o início e o fim. Portanto, cada data em que uma atividade existe recebe uma linha com acountespecífica- Agrupando todas as datas, agregando todas as atividades existentes e somando suas contagens
HAVINGfiltra 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
NULLvalor que é interpretado portsrangecomo 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
HAVINGclá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.