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

时间:2017-07-21 22:36:09

标签: mysql

我正在尝试计算单个用户的总登录时间。我有以下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中计算总时间。

2 个答案:

答案 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

图章表(通过指针设备设置):

Stamp table set through pointing device

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 for the query in phpmyadmin