在postgres中查找用户的总会话时间

Finding total session time of a user in postgres

我正在尝试创建一个查询,该查询将为我提供一列每个用户每个月的总登录时间。

username | auth_event_type |         time        | credential_id 

Joe      |       1         | 2021-11-01 09:00:00 | 44
Joe      |       2         | 2021-11-01 10:00:00 | 44
Jeff     |       1         | 2021-11-01 11:00:00 | 45
Jeff     |       2         | 2021-11-01 12:00:00 | 45
Joe      |       1         | 2021-11-01 12:00:00 | 46    
Joe      |       2         | 2021-11-01 12:30:00 | 46
Joe      |       1         | 2021-12-06 14:30:00 | 47
Joe      |       2         | 2021-12-06 15:30:00 | 47

auth_event_type 列指定事件是登录 (1) 还是注销 (2),credential_id 表示会话。

我正在尝试创建一个输出如下的查询:

username | year_month | total_time
Joe      | 2021-11    | 1:30
Jeff     | 2021-11    | 1:00
Joe      | 2021-12    | 1:00

我将如何在 postgres 中执行此操作?我认为它会涉及 window 功能?如果有人能指出我正确的方向,那就太好了。谢谢。

解决方案 1 部分有效

不确定 window 函数对您的情况是否有帮助,但聚合函数会 :

WITH list AS
(
SELECT username
     , date_trunc('month', time) AS year_month
     , max(time ORDER BY time) - min(time ORDER BY time) AS session_duration
  FROM your_table
 GROUP BY username, date_trunc('month', time), credential_id
 )
SELECT username
     , to_char (year_month, 'YYYY-MM') AS year_month
     , sum(session_duration) AS total_time
  FROM list
 GROUP BY username, year_month

查询的第一部分汇总了同一用户名 credential_id 的 login/logout 次,第二部分对 login/logout 之间的差值求和 year_month =] 次。此查询在登录时间和注销时间在同一个月之前运行良好,但在不同时失败。

解决方案 2 完全有效

为了计算每个用户名和每个月的 total_time,无论登录时间和注销时间是多少,我们可以使用时间范围方法,该方法将会话范围 [login_time, logout_time) 与每月范围相交[monthly_start_time, monthly_end_time) :

WITH monthly_range AS
(
SELECT to_char(m.month_start_date, 'YYYY-MM') AS month
     , tsrange(m.month_start_date, m.month_start_date+ interval '1 month' ) AS monthly_range
  FROM
     ( SELECT generate_series(min(date_trunc('month', time)), max(date_trunc('month', time)), '1 month') AS month_start_date
         FROM your_table
     ) AS m
), session_range AS
(
SELECT username
     , tsrange(min(time ORDER BY auth_event_type), max(time ORDER BY auth_event_type)) AS session_range
  FROM your_table
 GROUP BY username, credential_id
)
SELECT s.username
     , m.month
     , sum(upper(p.period) - lower(p.period)) AS total_time
  FROM monthly_range AS m
 INNER JOIN session_range AS s
    ON s.session_range && m.monthly_range
 CROSS JOIN LATERAL (SELECT s.session_range * m.monthly_range AS period) AS p
 GROUP BY s.username, m.month

结果见dbfiddle

使用 window 函数 lag() 对其进行分区 credential_idtime 排序,例如

WITH j AS (
  SELECT username, time, age(time, LAG(time) OVER w)
  FROM t
  WINDOW w AS (PARTITION BY credential_id ORDER BY time
               ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
) 
SELECT username, to_char(time,'yyyy-mm'),sum(age) FROM j
GROUP BY 1,2;

注意:框架ROWS BETWEEN 1 PRECEDING AND CURRENT ROW在这种情况下几乎是可选的,但保持window函数尽可能明确被认为是一个好习惯可能,以便将来您不必阅读文档来弄清楚您的查询在做什么。

演示:db<>fiddle