如何创建一个返回签入和签出之间时间差异的SQL查询?

时间:2014-11-26 08:20:53

标签: mysql sql

我使用的是mysql,而且我有一张类似这个的表格:

id  | user  | task | time                  | checkout
----+-------+------+-----------------------+---------
1   | 1     | 1    | 2014-11-25 17:00:00   | 0
2   | 2     | 2    | 2014-11-25 17:00:00   | 0
3   | 1     | 1    | 2014-11-25 18:00:00   | 1
4   | 1     | 2    | 2014-11-25 19:00:00   | 0
5   | 2     | 2    | 2014-11-25 20:00:00   | 1
6   | 1     | 2    | 2014-11-25 21:00:00   | 1
7   | 1     | 1    | 2014-11-25 21:00:00   | 0
8   | 1     | 1    | 2014-11-25 22:00:00   | 1

id只是一个自动生成的主键,如果该行注册了用户签入,则checkout为0;如果用户正在检出任务,则checkout为1。

我想知道如何创建一个查询,返回用户在每个任务上花费的时间,也就是说,我想知道checkout = 0时间和最近的时间之间的时差之和checkout =每个用户和任务的1次。

编辑:为了让事情变得更清晰,我对查询的期望结果将是:

user  | task | SUM(timedifference)
------+------+-----------------
1     | 1    | 02:00:00
1     | 2    | 02:00:00
2     | 2    | 03:00:00

我尝试过使用SUM(UNIX_TIMESTAMP(time) - UNIX_TIMESTAMP(time)),同时按用户和任务进行分组,以确定已经过了多长时间,但我不知道如何使查询只计算特定时间之间的差异我想要而不是全部。

有人可以帮忙吗?这有可能吗?

4 个答案:

答案 0 :(得分:2)

正如所有评论所述,您当前的表结构并不理想。然而,仍然可以将签到与结账配对。这是一个SQL服务器实现,但我相信你可以将它翻译成MySql:

SELECT id
    , user_id
    , task
    , minutes_per_each_task_instance = DATEDIFF(minute, time, (
            SELECT TOP 1 time
            FROM test AS checkout
            WHERE checkin.user_id = checkout.user_id 
                AND checkin.task = checkout.task 
                AND checkin.id < checkout.id 
                AND checkout.checkout = 1
            ))
FROM test AS checkin
WHERE checkin.checkout = 0

上面的代码可以工作,但随着你的桌子开始增长,它会越来越慢。在几十万之后它将变得显而易见

我建议将time列重命名为checkin,而不是让checkout布尔字段使其成为日期时间,并在用户结帐时更新记录。这样,您将拥有一半的记录数,并且没有复杂的逻辑来读取或查询

答案 1 :(得分:2)

您可以使用排名方法确定匹配的签入/签出记录是什么,并计算它们之间的时差

在我的示例中,new_table是表的名称


    SELECT n.user, n.task,n.time, n.checkout , 
           CASE WHEN @prev_user = n.user 
                 AND @prev_task = n.task 
                 AND @prev_checkout = 0 
                 AND n.checkout = 1 
                 AND @prev_time IS NOT NULL
                THEN HOUR(TIMEDIFF(n.time, @prev_time)) END AS timediff,
           @prev_time := n.time,
           @prev_user := n.user, 
           @prev_task := n.task, 
           @prev_checkout := n.checkout
      FROM new_table n, 
           (SELECT @prev_user = 0, @prev_task = 0, @prev_checkout = 0, @prev_time = NULL) a
    ORDER BY user, task, `time`

然后将时间差(timediff)包装在另一个选择

    SELECT x.user, x.task, sum(x.timediff) as total
      FROM ( 
            SELECT n.user, n.task,n.time, n.checkout , 
            CASE WHEN @prev_user = n.user 
                  AND @prev_task = n.task 
                  AND @prev_checkout = 0 
                  AND n.checkout = 1 
                  AND @prev_time IS NOT NULL
                 THEN HOUR(TIMEDIFF(n.time, @prev_time)) END AS timediff,
                  @prev_time := n.time,
                  @prev_user := n.user, 
                  @prev_task := n.task, 
                  @prev_checkout := n.checkout
             FROM new_table n,
                  (@prev_user = 0, @prev_task = 0, @prev_checkout = 0, @prev_time = NULL) a
            ORDER BY user, task, `time`
               ) x
      GROUP BY x.user, x.task

通过更改表结构可能更容易理解。如果可能的话。那么SQL就不会那么复杂,效率也会提高。但要回答你的问题,这是有可能的。 :)

在上面的示例中,名称前缀为&#39; @&#39;是MySQL变量,你可以使用&#39;:=&#39;将变量设置为值。很酷的东西?

答案 2 :(得分:0)

单独选择结帐和结帐的MAX,根据用户和任务进行映射并计算时差

select user, task, 
SUM(UNIX_TIMESTAMP(checkin.time) - UNIX_TIMESTAMP(checkout.time)) from (
(select user, task, MAX(time) as time
from checkouts
where checkout = 0
group by user, task) checkout
inner join
(select user, task, MAX(time) as time
from checkouts
where checkout = 1
group by user, task) checkin 
on (checkin.time > checkout.time 
and checkin.user = checkout.user 
and checkin.task = checkout.task)) c

答案 3 :(得分:0)

这应该有效。加入表并选择最短时间

SELECT 
  `user`,
  `task`,
  SUM(
    UNIX_TIMESTAMP(checkout) - UNIX_TIMESTAMP(checkin)
  ) 
FROM
  (SELECT 
    so1.`user`,
    so1.`task`,
    MIN(so1.`time`) AS checkin,
    MIN(so2.`time`) AS checkout 
  FROM
    so so1 
    INNER JOIN so so2 
      ON (
        so1.`id` = so2.`id` 
        AND so1.`user` = so2.`user` 
        AND so1.`task` = so2.`task` 
        AND so1.`checkout` = 0 
        AND so2.`checkout` = 1 
        AND so1.`time` < so2.`time`
      ) 
  GROUP BY `user`,
    `task`,
    so1.`time`) a 
GROUP BY `user`,
  `task` ;

正如其他人所建议的那样,这样做不会太好,如果开始处理更多数据,你需要调整它