简体   繁体   English

MySQL根据状态字段计算多行之间的时间差

[英]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

Can find everything at dbfiddle here可以在这里找到dbfiddle 的所有内容

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 :结果:

phpmyadmin 中查询的结果

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM