我正在尝试计算单个用户的总登录时间。我有以下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
客户希望计算用户在所选日期内登录的时间。在做一些案例研究时,我能够计算首次登录/注销之间的时间:
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
但是,从示例数据中可以看出,用户可以在白天进行多次登录/注销,以及多次中断时间。我如何仅使用MySQL聚合登录时间。我用PHP完成了这个,但由于服务器性能问题(有很多记录),我必须弄清楚如何在MySQL中计算总时间。
答案 0 :(得分:2)
这不是一个简单的查询,所以,让我们一步一步地做到这一点:
<强>方案强>
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'),
-- An another one, that doesn't logout (i.e.: it is *now* logged in)
(654, now() - interval 33 minute, 'login');
第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 ;
user_id | login_time | logout_time ------: | :------------------ | :------------------ 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 ;
user_id | login_time | logout_time | logged_in_time | 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 ;
user_id | total_seconds_logged_in_time ------: | ---------------------------: 472 | 18000 532 | 1800 654 | 1980
第4步
我们为休息时间执行相同的操作
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 ;
user_id | total_seconds_break_time ------: | -----------------------: 472 | 1800
最后一步:
(第3步查询)和LEFT JOIN
使用(第4步查询)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 ;
user_id | net_total_seconds_logged_in_time ------: | -------------------------------: 472 | 16200 532 | 1800 654 | 1980
可以在 dbfiddle here
找到所有内容答案 1 :(得分:0)
根据@joanolo 的回答,我编写了一个查询来计算给定时间段(通常为 1 个月)内员工的存在情况。
我使用语句 WITH
(自 MySQL 8.x 起可用)优化查询以获取第一个过滤的图章列表并使用 UNIX_TIMESTAMP()
转换它们。
因此,它的执行时间减少到 1.2 秒(大约 3900 条记录,6 个月,16 名员工,1CPU)。
表匹配外键 fk_time_type_id
:
ID | time_type
------------------
3 | start working
7 | go to break
8 | go to lunch
9 | back from break
10 | in meeting
11 | end working
图章表(通过指针设备设置):
MySQL 查询:
WITH
`stamps` AS (
SELECT
UNIX_TIMESTAMP(`stamp`) AS `stamp`,
`time_stamp_id`,
`fk_employee_id`,
`fk_time_type_id`
FROM `time__stamp`
WHERE DATE(`stamp`) BETWEEN '2021-03-25' AND '2021-04-24'
AND `fk_time_type_id` IN(3,7,8,9,10,11)
)
SELECT
UA.`user_account_id`,
UA.`firstname`,
UA.`lastname`,
UA.`email`,
SEC_TO_TIME(SUM(TS.`out` - TS.`in`)) AS `in_time`,
SUM(TS.`out` - TS.`in`) AS `in_seconds`,
SUM(TS.`out` - TS.`in`) / 3600 AS `in_dec`
FROM
(SELECT
`time_stamp_id` AS `id`,
`fk_employee_id` AS `fk_employee_id`,
`stamp` AS `in`,
coalesce(
(
SELECT `stamp`
FROM `stamps` `t_out`
WHERE `t_out`.`fk_employee_id` = `t_in`.`fk_employee_id`
AND `t_out`.`stamp` >= `t_in`.`stamp`
AND `t_out`.`fk_time_type_id` IN(7,8,11)
ORDER BY `stamp`
LIMIT 1
),
UNIX_TIMESTAMP(NOW())
) AS `out`
FROM `stamps` `t_in`
WHERE `fk_time_type_id` IN(3,9,10)
) TS
INNER JOIN `user__account` UA
ON TS.`fk_employee_id` = UA.`user_account_id`
GROUP BY TS.`fk_employee_id`
结果: