计算最大并发用户会话数

Calculate maximum number of concurrent user sessions

我在 PostgreSQL 9.6 中有一个 UserSession table 存储用户的登录和注销时间,我想计算并发会话的最大数量 - 只有当它们 重叠至少30分钟.

例子

userid      |  starttime                |  endtime  
------------+---------------------------+--------------------------
1           |  01-Oct-19 6:00:00 AM     |    01-Oct-19 11:10:00 AM  
2           |  01-Oct-19 11:00:00 AM    |    01-Oct-19 4:00:00 PM 
3           |  01-Oct-19 10:30:00 AM    |    01-Oct-19 4:00:00 PM 

此处,会话 1 和会话 2 不是并发的,因为它们仅重叠 10 分钟,会话 1 和 3 是并发的,因为它们重叠超过 30 分钟,所以 结果是 2 个并发会话

注意: 只有当所有 n 个会话重叠至少 30 分钟时,结果才会为 n。

Table定义

CREATE TABLE UserSessions (
    SessionID bigserial NOT NULL,
    UserID bigint NOT NULL,
    StartTime timestamp NOT NULL,
    EndTime timestamp NULL,
    OrganisationID bigint NOT NULL,
    CONSTRAINT PK_SessionsID PRIMARY KEY(SessionID),
    CONSTRAINT FK_UserID FOREIGN KEY(UserID) REFERENCES GlobalUsers(UserID),
    CONSTRAINT FK_OrganisationID FOREIGN KEY(OrganisationID) REFERENCES Organisations(OrganisationID)
);

类似问题

这里有一个类似的问题:,但在同一时间点有并发手段,在我的情况下,我需要检查它们是否重叠至少 30 分钟

从每个时间范围的结束(或开始)开始减去 30 分钟。然后基本上按照我的 中概述的方式进行(在所有地方都朝着正确的方向调整 30 分钟)。短于 30 分钟的范围会先验地被排除 - 这是有道理的,因为这些范围永远不会成为 30 分钟连续重叠期间的一部分。也使查询更快。

计算 2019 年 10 月的所有天数(示例范围):

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 here

请注意 LOCALTIMESTAMP 取决于当前会话的时区。考虑在 table 和 CURRENT_TIMESTAMP 中使用 timestamptz。参见:

  • Ignoring time zones altogether in Rails and PostgreSQL