如何在MySQL DATETIME列中检测连续小时数?

时间:2014-02-19 20:11:35

标签: mysql sql

我们有一个这样的表:

DESCRIBE time_slots;

  id          int(11)
  user_id     int(11)
  start_time  datetime

start_time字段始终为小时增量(例如2013-09-04 16:00:00

我们的数据科学家希望以每start_time个标识连续user_id个记录的方式查询此表,以便她可以创建如下所示的派生表:

  id          int(11)
  user_id     int(11)
  start_time  datetime
  end_time    datetime

例如,鉴于此数据:

user_id: 5, start_time: 2013-09-04 16:00:00
user_id: 5, start_time: 2013-09-04 17:00:00
user_id: 5, start_time: 2013-09-04 18:00:00

user_id: 6, start_time: 2013-09-04 16:00:00
user_id: 6, start_time: 2013-09-04 17:00:00
user_id: 6, start_time: 2013-09-04 18:00:00

user_id: 6, start_time: 2013-09-04 20:00:00
user_id: 6, start_time: 2013-09-04 21:00:00
user_id: 6, start_time: 2013-09-04 22:00:00

...我们可以推导出这个输出:

user_id: 5, start_time: 2013-09-04 16:00:00, end_time: 2013-09-04 18:00:00
user_id: 6, start_time: 2013-09-04 16:00:00, end_time: 2013-09-04 18:00:00
user_id: 6, start_time: 2013-09-04 20:00:00, end_time: 2013-09-04 22:00:00

每天给定用户可能有多个这些开始/结束“块”(但它们不会重叠)。

在我转到Plan B(设置非规范化数据仓库)之前,有什么想法可以在SQL中完成吗?

2 个答案:

答案 0 :(得分:0)

根据您的数据库...窗口函数可以实现此目的。生成一个表示前一列的增量的列(因此您需要按user_id,startTime排序);然后,您可以使用该增量列对组进行分组。由于连续块在delta中将由“1”表示,而新块将具有更高的数字。

您也可以通过使用子选择进行连接并将其偏移1来实现此目的,例如加入ROW_NUMBER和ROW_NUMBER-1然后您可以计算时间戳之间的差值,并使用outerselect来处理一些魔法得到你想要的。关键是三角洲。

您可以这样做:

SET @prevUser := null;
SET @prevStartTime := 0;
SET @groupNumber := 1;
SET @groupPrevUser := null;


select 
    user,
    groupNumber,
    min(startTime),
    max(endTime),
    max(endTime) - min(startTime) as 'duration'
from
    (SELECT 
        user,
            startTime,
            endTime,
            delta,
            IF(delta != 10000 || @groupPrevUser <> user, @groupNumber:=@groupNumber + 1, @groupNumber) 'groupNumber',
            @groupPrevUser:=user
    from
        (SELECT 
        user,
            startTime,
            endTime,
            IF(@prevUser <> user || @prevStartTime = 0, endTime - startTime, startTime - @prevStartTime) AS delta,
            @prevUser:=user,
            @prevStartTime:=startTime
    FROM
        queries
    ORDER BY user , startTime) userData) userGroupData
group by user , groupNumber

获得此结果:

# user, groupNumber, min(startTime), max(endTime), duration
bob, 1, 1392060000, 1392080000, 20000
bob, 2, 1392090000, 1392100000, 10000
jim, 3, 1392150000, 1392180000, 30000

使用此基表:

# user, startTime, endTime
bob, 1392060000, 1392070000
bob, 1392070000, 1392080000
bob, 1392090000, 1392100000
jim, 1392150000, 1392160000
jim, 1392160000, 1392170000
jim, 1392170000, 1392180000

答案 1 :(得分:0)

我的第一个建议是更改架构以为block_by_id添加计数器。然后你的问题是一个简单的最小 - 最大。并且当创建记录时,可以通过查看(1)是否已存在该user_id的记录来确定块编号,并且(2)是否比新记录早一个小时。我想你可以看到这是非规范化的,在这种情况下,我的想法是“在运行中”找出块。

SELECT user_id, MIN(start_time) AS start_time, MAX(start_time) AS start_time
FROM time_slots t1
WHERE NOT EXISTS 
    (SELECT 1 FROM time_slots AS t2 WHERE t1.user_id = t2.user_id
       AND timestampdiff(HOUR, t1.start_time, t2.start_time)=1
/* replace with date arithmetic function of your RDBMS if need be */ 
    )
GROUP BY user_id;

我没有调整MySQL的经验。可能是不同的timediff表达式允许它使用(user_id, start_time)上的索引。