2016-03-02 52 views
3

我有一张名为userActivity的表格,其中记录了每个活动期间。如何在MySQL中计算明智的可用时间?

这里的表结构:

表:userActivity

ID  user_id  start_time end_time 

当用户进入在线start time被记录下来,只要在线状态改变时,end time被记录在相应的行。

现在我要生成一个报告,显示用户每天的可用时间。

样品输入:

ID  user_id   start_time    end_time 

'1'  '1'  '2016-02-28 10:00:00' '2016-02-28 19:00:00' 
'2'  '1'  '2016-02-28 22:00:00' '2016-02-29 10:00:00' 
'3'  '1'  '2016-03-02 10:00:00' '2016-03-02 19:00:00' 
'4'  '1'  '2016-03-02 22:00:00' '2016-03-06 19:00:00' 

预期输出:

Date   AvailableTime(Hours) 
2016-02-28   11 
2016-02-29   10 
2016-03-02   11 
2016-03-03   24 
2016-03-04   24 
2016-03-05   24 
2016-03-06   19 

到目前为止,我已经试过:

SELECT 
DATE_FORMAT(start_time,"%Y-%m-%d") `date`, 
TIMESTAMPDIFF(HOUR,start_time,end_time) availableTime 
FROM useractivity 
GROUP BY `date` 

得到输出:

Date   availableTime(Hours) 

2016-02-28  9 
2016-03-02  9 

这里的SQL FIDDLE

注: 请忽略user_id暂且。我可以在应用程序级别解决它,但我想在MySQL中处理它。

的时间间隔就可以开始一天,并结束超过一天后

总之,的可用时间是刚好在天轴上的投影(从开始时间和结束时间)。如果开始时间没有投影到结束时间的同一天,则开始时间将被视为结束时间投影到的特定日期的开始时间。

描绘图:

enter image description here

所以可用的时间会从这张截图如下计算:

28 Feb = (t2-t1) + (t4- t3) 

29 Feb = (t5 - t4) 

02 Mar = (t7 - t6) 
+0

你得到的印象分增加一个小提琴,但它不会使你的问题更清楚。 –

+0

时间间隔是否可以在一天后开始并在一天后结束? (即开始16:th,结束22:nd? –

+0

是的这种情况是可能的。@JoachimIsaksson –

回答

0

我创建的UNION ALL

SELECT sub_query.`date`, SUM(sub_query.available_time) FROM (
    SELECT 
     DATE_FORMAT(start_time,"%Y-%m-%d") `date`, 
     IF(TIMESTAMPDIFF(day,date(start_time),date(end_time))= 0, 
      TIMESTAMPDIFF(HOUR,start_time,end_time),0) AS available_time 
    FROM useractivity 

    UNION ALL 

    SELECT 
     DATE_FORMAT(start_time,"%Y-%m-%d") `date`, 
     IF(TIMESTAMPDIFF(day,date(start_time),date(end_time)) > 0, 
      TIMESTAMPDIFF(HOUR,start_time, date_add(date(start_time),interval 24 hour)),0) AS available_time 
    FROM useractivity 

    UNION ALL 

    SELECT 
     DATE_FORMAT(end_time,"%Y-%m-%d") `date`, 
     IF(TIMESTAMPDIFF(day,date(start_time),date(end_time)) > 0, 
      TIMESTAMPDIFF(HOUR,date(end_time), end_time) , 0) AS available_time 
    FROM useractivity 
) AS sub_query 
GROUP BY sub_query.`date` 

UNION 

SELECT SELECTed_date `date`, 24 FROM 
(SELECT adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) SELECTed_date FROM 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0, 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1, 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2, 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3, 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4) v 
WHERE SELECTed_date between (SELECT min(date(start_time)) FROM useractivity) and (SELECT max(date(end_time)) FROM useractivity) 
AND SELECTed_date NOT IN(
SELECT miss_date FROM (
    SELECT date(start_time) AS miss_date FROM useractivity 
    UNION 
    SELECT date(end_time) AS miss_date FROM useractivity 
) AS miss 
) ORDER BY `date`; 
查询帮助

SQLFiddle

+0

感谢您的回答,但是我想要一个符合要求的解决方案,请参阅我在文章中附上的截图。@VipinJain –

+0

请运行查询另一个插入语句'INSERT INTO'useractivity' VALUES('4','1','2016-03-02 22:00:00','2016-03-06 19:00:00');' –

+0

根据我的quey在2016-03-02的输出是83.应该预期的结果是什么? –

1

您可以使用日期表进行交叉连接,以在要从日志时间分割的那一天获取真正的开始和结束时间。

CREATE TABLE `dates` (
    `date` date , 
    `start_time` timestamp , 
    `end_time` timestamp 
); 

INSERT INTO `dates` VALUES('20160228','2016-02-28 00:00:00', '2016-02-29 00:00:00'); 
INSERT INTO `dates` VALUES('20160229','2016-02-29 00:00:00', '2016-03-01 00:00:00'); 
INSERT INTO `dates` VALUES('20160301','2016-03-01 00:00:00', '2016-03-02 00:00:00'); 
INSERT INTO `dates` VALUES('20160302','2016-03-02 00:00:00', '2016-03-03 00:00:00'); 
INSERT INTO `dates` VALUES('20160303','2016-03-03 00:00:00', '2016-03-04 00:00:00'); 
INSERT INTO `dates` VALUES('20160304','2016-03-04 00:00:00', '2016-03-05 00:00:00'); 
INSERT INTO `dates` VALUES('20160305','2016-03-05 00:00:00', '2016-03-06 00:00:00'); 
INSERT INTO `dates` VALUES('20160306','2016-03-06 00:00:00', '2016-03-07 00:00:00'); 

SELECT 
    u.*, 
    d.date, 
    case when u.start_time<= d.start_time then d.start_time 
     else u.start_time end as `start_time_in_the_day`, 
    case when u.end_time> d.end_time then d.end_time 
     else u.end_time end as `end_time_in_the_day` 
FROM useractivity u 
INNER JOIN dates d 
ON u.start_time< d.end_time 
    and u.end_time>= d.start_time 

然后你只需要总结end_time_in_the_daystart_time_in_the_day之间的时间。

SELECT 
    user_id, 
    date, 
    sum(TIMESTAMPDIFF(HOUR,start_time_in_the_day,end_time_in_the_day)) as `availableTime` 
FROM(
    SELECT 
     u.*, 
     d.date, 
     case when u.start_time<= d.start_time then d.start_time 
      else u.start_time end as `start_time_in_the_day`, 
     case when u.end_time> d.end_time then d.end_time 
      else u.end_time end as `end_time_in_the_day` 
    FROM useractivity u 
    INNER JOIN dates d 
    ON u.start_time< d.end_time 
     and u.end_time>= d.start_time) as t 
group by user_id,date 

SqlFiddle here.

而且我觉得应用TIMESTAMPDIFF(SECOND...代替TIMESTAMPDIFF(HOUR...会更好。

+0

非常感谢。这是另一种方式,但我认为Vipin的答案更具可扩展性。 +1 –

+0

实际上我会用'MILLISECOND'单位计算可用时间。为了可读性,我添加了'HOUR'单位。 –

+0

@AnonymousOne Vipin的回答也有效。但我认为使用数据模型来讲述故事要比使用复杂算法好得多。 –

0

我修改@Vipin耆那教的查询来实现这个要求:

SELECT sub_query.`date`, SUM(sub_query.available_time) FROM (
    SELECT 
     DATE_FORMAT(start_time,"%Y-%m-%d") `date`, 
     IF(TIMESTAMPDIFF(day,date(start_time),date(end_time))= 0, 
      TIMESTAMPDIFF(HOUR,start_time,end_time),0) AS available_time 
    FROM useractivity 

    UNION ALL 

    SELECT 
     DATE_FORMAT(start_time,"%Y-%m-%d") `date`, 
     IF(TIMESTAMPDIFF(day,date(start_time),date(end_time)) > 0, 
      TIMESTAMPDIFF(HOUR,start_time, date_add(date(start_time),interval 24 hour)),0) AS available_time 
    FROM useractivity 

    UNION ALL 

    SELECT 
     DATE_FORMAT(end_time,"%Y-%m-%d") `date`, 
     IF(TIMESTAMPDIFF(day,date(start_time),date(end_time)) > 0, 
      TIMESTAMPDIFF(HOUR,date(end_time), end_time) , 0) AS available_time 
    FROM useractivity 
) AS sub_query 
GROUP BY sub_query.`date` 

UNION 

SELECT SELECTed_date `date`, 24 FROM 
(SELECT adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) SELECTed_date FROM 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t0, 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1, 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2, 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3, 
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t4) v 
WHERE SELECTed_date between (SELECT min(date(start_time)) FROM useractivity) and (SELECT max(date(end_time)) FROM useractivity) 
AND SELECTed_date NOT IN(
SELECT miss_date FROM (
    SELECT date(start_time) AS miss_date FROM useractivity 
    UNION 
    SELECT date(end_time) AS miss_date FROM useractivity 
) AS miss 
) 
AND EXISTS (SELECT 1 FROM useractivity WHERE SELECTed_date BETWEEN start_time AND end_time) 

ORDER BY `date`;