Deduza 30 minutos do final (ou início) de cada intervalo de tempo. Então, basicamente, prossiga conforme descrito na minha resposta "simples" referenciada (ajustando para os 30 min na direção certa em todos os lugares). Intervalos inferiores a 30 minutos são eliminados a priori - o que faz sentido, pois nunca podem fazer parte de um período de 30 minutos de sobreposição contínua. Também torna a consulta mais rápida.
Calculando para todos os dias em outubro de 2019 (intervalo de exemplo):
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 aqui
Esteja ciente de que
LOCALTIMESTAMP
depende do fuso horário da sessão atual. Considere usar timestamptz em sua tabela e CURRENT_TIMESTAMP
em vez de. Ver: