在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_id
按 time
排序,例如
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
我正在尝试创建一个查询,该查询将为我提供一列每个用户每个月的总登录时间。
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_id
按 time
排序,例如
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