demostración:db<>fiddle (utiliza el conjunto de datos anterior con la parte A-B superpuesta)
Descargo de responsabilidad: Esto funciona para intervalos de días, no para marcas de tiempo. El requisito de ts llegó más tarde.
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 todas las fechas entre el inicio y el final. Entonces, cada fecha en que existe una actividad obtiene una fila con elcount
específico- Agrupación de todas las fechas, agregación de todas las actividades existentes y suma de sus recuentos
HAVING
filtra las fechas en las que solo existe una actividad- Debido a que hay diferentes días con las mismas actividades, solo necesitamos un representante:filtrar todos los duplicados con
DISTINCT ON
- Combina este resultado con la tabla original para obtener el inicio y el final. (tenga en cuenta que "fin" es una palabra reservada en Postgres, ¡mejor debería encontrar otro nombre de columna!). Era más cómodo perderlos antes, pero es posible obtener estos datos dentro de la subconsulta.
- Agrupe esta unión para obtener la fecha más temprana y más reciente de cada intervalo.
Aquí hay una versión para las marcas de tiempo:
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
La idea principal es identificar posibles franjas horarias. Así que tomo cada tiempo conocido (tanto el inicio como el final) y los pongo en una lista ordenada. Entonces puedo tomar los primeros dos tiempos conocidos (17:00 desde el inicio A y 18:00 desde el inicio B) y verificar qué intervalo está en él. Luego compruebo el 2.° y el 3.°, luego el 3.° y el 4.° y así sucesivamente.
En el primer intervalo de tiempo sólo cabe A. En el segundo de 18-19 también encaja B. En el siguiente slot 19-20 también C, del 20 al 20:30 ya no cabe A, solo B y C. El siguiente es 20:30-22 donde solo cabe B, finalmente se añade 22-23 D B y, por último, pero no menos importante, solo D encaja en 23-23:30.
Así que tomo esta lista de tiempo y la combino con la tabla de actividades donde se cruzan los intervalos. Después de eso, es solo una agrupación por intervalo de tiempo y un resumen de su cuenta.
- esto pone ambos ts de una fila en una matriz cuyos elementos se expanden en una fila por elemento con
unnest
. Así que tengo todos los tiempos en una columna que se puede ordenar simplemente - utilizando la función de ventana
permite tomar el valor de la siguiente fila en la actual. Entonces puedo crear un rango de marca de tiempo a partir de estos dos valores con
tsrange
- Este filtro es necesario porque la última fila no tiene "valor siguiente". Esto crea un
NULL
valor que es interpretado portsrange
como infinito. Así que esto crearía una increíble franja horaria equivocada. Entonces necesitamos filtrar esta fila. - Únete a las franjas horarias contra la mesa original. El
&&
el operador verifica si dos tipos de rango se superponen. - Agrupación por intervalos de tiempo únicos, agregando los nombres y el conteo. Filtre los intervalos de tiempo con una sola actividad usando el
HAVING
cláusula - Es un poco complicado obtener los puntos de inicio y final correctos. Entonces, los puntos de inicio son el máximo del inicio de la actividad o el comienzo de un intervalo de tiempo (que se puede obtener usando
lower
). P.ej. Tome la franja 20-20:30:Comienza a las 20h pero ni B ni C tienen su punto de partida allí. Similar a la hora de finalización.