我正在尝试计算单个用户的总登录时间。我有以下 MySQL 表:
user_id | timelog | status
------- | ------------------- | ------
472 | 2017-07-18 08:00:00 | login
472 | 2017-07-18 09:00:00 | break start
472 | 2017-07-18 09:30:00 | break end
472 | 2017-07-18 10:00:00 | logout
472 | 2017-07-18 11:00:00 | login
472 | 2017-07-18 14:00:00 | logout
Run Code Online (Sandbox Code Playgroud)
客户端想要计算用户在选定日期内登录所花费的时间。在做一些案例研究时,我能够计算出第一次登录/注销之间的时间:
SELECT
TIMEDIFF(
(SELECT timelog FROM qc_user_status_logs WHERE status = 'logout' AND user_id = '472' AND timelog LIKE '2017-07-18%' LIMIT 0,1),
(SELECT timelog FROM qc_user_status_logs WHERE status = 'login' AND user_id = '472' AND timelog LIKE '2017-07-18%' LIMIT 0,1)
) as loggedInTime
Run Code Online (Sandbox Code Playgroud)
但是,从示例数据中可以看出,用户在一天中可以有多次登录/注销,以及多次休息时间。我将如何仅使用 MySQL 聚合登录时间。我已经使用 PHP 实现了这一点,但是由于服务器性能问题(有很多记录),我必须找出在 MySQL 中计算总时间的方法。
这不是一个简单的查询,因此,让我们一步一步地进行:
设想
CREATE TABLE qc_user_status_logs
(
user_id integer,
timelog datetime,
status varchar(15)
) ;
INSERT INTO qc_user_status_logs
(user_id, timelog, status)
VALUES
-- Your example data
(472, '2017-07-18 08:00:00', 'login'),
(472, '2017-07-18 09:00:00', 'break start'),
(472, '2017-07-18 09:30:00', 'break end'),
(472, '2017-07-18 10:00:00', 'logout'),
(472, '2017-07-18 11:00:00', 'login'),
(472, '2017-07-18 14:00:00', 'logout'),
-- An extra user
(532, '2017-07-18 09:00:00', 'login'),
(532, '2017-07-18 09:30:00', 'logout'),
-- And another entry for a user that doesn't log out
-- (i.e.: it is *now* logged in)
(654, now() - interval 33 minute, 'login');
Run Code Online (Sandbox Code Playgroud)
第1步
对于每次登录,通过子查询找到相应的注销(在 MariaDB 中,您将使用窗口函数)
SELECT
user_id,
timelog AS login_time,
coalesce(
(SELECT timelog
FROM qc_user_status_logs t_out
WHERE t_out.user_id = t_in.user_id
AND t_out.timelog >= t_in.timelog
AND t_out.status = 'logout'
ORDER BY timelog
LIMIT 1
),
now()
) AS logout_time
FROM
qc_user_status_logs t_in
WHERE
status = 'login'
ORDER BY
user_id, timelog ;
Run Code Online (Sandbox Code Playgroud)
用户 ID | 登录时间 | 登出时间
------: | :------------------ | :------------------
第472话 2017-07-18 08:00:00 | 2017-07-18 10:00:00
第472话 2017-07-18 11:00:00 | 2017-07-18 14:00:00
第532话 2017-07-18 09:00:00 | 2017-07-18 09:30:00
第654话 2017-07-21 23:38:53 | 2017-07-22 00:11:53
第2步
将“登录/注销”时间转换为“登录”间隔。最好的方法是将时间转换为 unix_times 并减去。结果将是登录和注销之间的秒数:
SELECT
user_id,
login_time,
logout_time,
timediff(logout_time, login_time) AS logged_in_time,
unix_timestamp(logout_time) - unix_timestamp(login_time) AS seconds_logged_in_time
FROM
(SELECT
user_id,
timelog AS login_time,
coalesce(
(SELECT timelog
FROM qc_user_status_logs t_out
WHERE t_out.user_id = t_in.user_id
AND t_out.timelog >= t_in.timelog
AND t_out.status = 'logout'
ORDER BY timelog
LIMIT 1
),
now()
) AS logout_time
FROM
qc_user_status_logs t_in
WHERE
status = 'login'
) AS q1
ORDER BY
user_id, login_time ;
Run Code Online (Sandbox Code Playgroud)
用户 ID | 登录时间 | 登出时间 | 登录时间 | seconds_logged_in_time
------: | :------------------ | :------------------ | :------------- | ---------------------:
第472话 2017-07-18 08:00:00 | 2017-07-18 10:00:00 | 02:00:00 | 7200
第472话 2017-07-18 11:00:00 | 2017-07-18 14:00:00 | 03:00:00 | 10800
第532话 2017-07-18 09:00:00 | 2017-07-18 09:30:00 | 00:30:00 | 1800
第654话 2017-07-21 23:38:53 | 2017-07-22 00:11:53 | 00:33:00 | 1980年
第 3 步
从上一个查询,聚合(添加)登录间隔,按用户分组
SELECT
user_id,
sum(unix_timestamp(logout_time) - unix_timestamp(login_time)) AS total_seconds_logged_in_time
FROM
(SELECT
user_id,
timelog AS login_time,
coalesce(
(SELECT timelog
FROM qc_user_status_logs t_out
WHERE t_out.user_id = t_in.user_id
AND t_out.timelog >= t_in.timelog
AND t_out.status = 'logout'
ORDER BY timelog
LIMIT 1
),
now()
) AS logout_time
FROM
qc_user_status_logs t_in
WHERE
status = 'login'
) AS q1
GROUP BY
user_id
ORDER BY
user_id ;
Run Code Online (Sandbox Code Playgroud)
用户 ID | total_seconds_logged_in_time
------: | ---------------------------:
第472话 18000
第532话 1800
第654话 1980年
第四步
我们在休息时做同样的事情
SELECT
user_id,
sum(unix_timestamp(logout_time) - unix_timestamp(login_time)) AS total_seconds_break_time
FROM
(SELECT
user_id,
timelog AS login_time,
coalesce(
(SELECT timelog
FROM qc_user_status_logs t_out
WHERE t_out.user_id = t_in.user_id
AND t_out.timelog >= t_in.timelog
AND t_out.status = 'break end'
ORDER BY timelog
LIMIT 1
),
now()
) AS logout_time
FROM
qc_user_status_logs t_in
WHERE
status = 'break start'
) AS q1
GROUP BY
user_id
ORDER BY
user_id ;
Run Code Online (Sandbox Code Playgroud)
用户 ID | total_seconds_break_time
------: | -----------------------:
第472话 1800
最后一步:
取 (step 3 query) 和LEFT JOINit with the (step 4 query) ON user_id,这样我们就可以将所有的信息对应user_id起来。
减去total_seconds_break_time(或 0,如果没有任何中断;使用coalesce)。
这会给你最终的结果:
SELECT
q10.user_id,
q10.total_seconds_logged_in_time -
coalesce(q20.total_seconds_break_time, 0) AS net_total_seconds_logged_in_time
FROM
(SELECT
user_id,
sum(unix_timestamp(logout_time) - unix_timestamp(login_time)) AS total_seconds_logged_in_time
FROM
(SELECT
user_id,
timelog AS login_time,
coalesce(
(SELECT timelog
FROM qc_user_status_logs t_out
WHERE t_out.user_id = t_in.user_id
AND t_out.timelog >= t_in.timelog
AND t_out.status = 'logout'
ORDER BY timelog
LIMIT 1
),
now()
) AS logout_time
FROM
qc_user_status_logs t_in
WHERE
status = 'login'
) AS q1
GROUP BY
user_id
) AS q10
LEFT JOIN
(SELECT
user_id,
sum(unix_timestamp(logout_time) - unix_timestamp(login_time)) AS total_seconds_break_time
FROM
(SELECT
user_id,
timelog AS login_time,
coalesce(
(SELECT timelog
FROM qc_user_status_logs t_out
WHERE t_out.user_id = t_in.user_id
AND t_out.timelog >= t_in.timelog
AND t_out.status = 'break end'
ORDER BY timelog
LIMIT 1
),
now()
) AS logout_time
FROM
qc_user_status_logs t_in
WHERE
status = 'break start'
) AS q1
GROUP BY
user_id
) AS q20 ON q20.user_id = q10.user_id
ORDER BY
q10.user_id ;
Run Code Online (Sandbox Code Playgroud)
用户 ID | net_total_seconds_logged_in_time
------: | -------------------------------:
第472话 16200
第532话 1800
第654话 1980年
可以在这里找到dbfiddle 的所有内容