[英]MySQL calculate time difference between multiple rows based on status field
I'm trying to calculate total logged in time for individual users.我正在尝试计算单个用户的总登录时间。 I have the following MySQL table:我有以下 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
The client wants to calculate the time the user has spent logged in within selected date.客户端想要计算用户在选定日期内登录所花费的时间。 While doing some case studies, I was able to calculate the time between the first login/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
However, as you can see from the example data, the user can have multiple logings/logouts during the day, as well as multiple break times.但是,从示例数据中可以看出,用户在一天中可以有多次登录/注销,以及多次休息时间。 How would I aggregate the logged in time using MySQL only.我将如何仅使用 MySQL 聚合登录时间。 I've accomplished this using PHP, however because of server performance issues (there's a lot of records), I have to figure out the way to get the total time calculated in MySQL.我已经使用 PHP 实现了这一点,但是由于服务器性能问题(有很多记录),我必须找出在 MySQL 中计算总时间的方法。
This isn't a straightforward query, so, let's do it with a step-by-step approach:这不是一个简单的查询,因此,让我们一步一步地进行:
Scenario场景
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');
Step 1第 1 步
For each login, find the corresponding logout via subquery (in MariaDB, you'd use a window function)对于每次登录,通过子查询找到相应的注销(在 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
Step 2第 2 步
Convert 'login/logout' times to 'logged in' intervals.将“登录/注销”时间转换为“登录”间隔。 Best way is to convert times to unix_times and subtract.最好的方法是将时间转换为 unix_times 并减去。 The result will be number of seconds between login and logout:结果将是登录和注销之间的秒数:
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
Step 3第 3 步
From the previous query, aggregate (add) logged in intervals, grouping by user从上一个查询,聚合(添加)登录间隔,按用户分组
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
Step 4第 4 步
We perform the same thing for breaks我们在休息时做同样的事情
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
Final step:最后一步:
Take the (step 3 query) and LEFT JOIN
it with the (step 4 query) ON user_id
, so that we have all the information corresponding to every user_id
together.取(第 3 步查询)并将其与(第 4 步查询) ON user_id
LEFT JOIN
,以便我们将与每个user_id
对应的所有信息放在一起。
Subtract the total_seconds_break_time
(or 0, if there isn't any break; by using coalesce
).减去total_seconds_break_time
(或 0,如果没有任何中断;通过使用coalesce
)。
That will give you the final result:这会给你最终的结果:
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
Based on @joanolo answer, I wrote a query to calculate presence of employees for a given period (usually 1 month).根据@joanolo 的回答,我编写了一个查询来计算给定时间段(通常为 1 个月)内员工的存在情况。
I optimised the query using statement WITH
(available since MySQL 8.x) to get a first filtered list of stamps and converting them with UNIX_TIMESTAMP()
.我使用WITH
语句优化查询(自 MySQL 8.x 起可用)以获得第一个过滤的邮票列表并使用UNIX_TIMESTAMP()
转换它们。 Thus, its execution is reduced to 1.2 second (on about 3900 records, 6 months, 16 employees, 1CPU).因此,它的执行时间减少到 1.2 秒(大约 3900 条记录,6 个月,16 名员工,1CPU)。
Table matching with foreign key fk_time_type_id
:与外键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
Stamp table (set through pointing device) :印章表(通过指点设备设置):
MySQL query : 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`
Result :结果:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.