Deduzca 30 minutos del final (o inicio) de cada rango de tiempo. Luego, básicamente proceda como se describe en mi respuesta "simple" referenciada (ajustando los 30 min en la dirección correcta en todas partes). Los rangos de menos de 30 minutos se eliminan a priori, lo que tiene sentido ya que nunca pueden ser parte de un período de 30 minutos de superposición continua. También hace que la consulta sea más rápida.
Cálculo para todos los días de octubre de 2019 (rango de ejemplo):
WITH range AS (SELECT timestamp '2019-10-01' AS start_ts -- incl. lower bound
, timestamp '2019-11-01' AS end_ts) -- excl. upper bound
, cte AS (
SELECT userid, starttime
-- default to current timestamp if NULL
, COALESCE(endtime, localtimestamp) - interval '30 min' AS endtime
FROM usersessions, range r
WHERE starttime < r.end_ts -- count overlaps *starting* in outer time range
AND (endtime >= r.start_ts + interval '30 min' OR endtime IS NULL)
)
, ct AS (
SELECT ts, sum(ct) OVER (ORDER BY ts, ct) AS session_ct
FROM (
SELECT endtime AS ts, -1 AS ct FROM cte
UNION ALL
SELECT starttime , +1 FROM cte
) sub
)
SELECT ts::date, max(session_ct) AS max_concurrent_sessions
FROM ct, range r
WHERE ts >= r.start_ts
AND ts < r.end_ts -- crop outer time range
GROUP BY ts::date
ORDER BY 1;
db<>fiddle aquí
Tenga en cuenta que LOCALTIMESTAMP
depende de la zona horaria de la sesión actual. Considere usar timestamptz en su tabla y CURRENT_TIMESTAMP
en cambio. Ver: