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

Posted

技术标签:

【中文标题】如何检测 MySQL DATETIME 列中的连续小时数?【英文标题】:How can I detect consecutive hours in a MySQL DATETIME column? 【发布时间】:2014-02-19 20:11:35 【问题描述】:

我们有一张这样的表:

描述 time_slots; id int(11) user_id int(11) start_time 日期时间

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

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

id int(11) user_id int(11) start_time 日期时间 end_time 日期时间

例如,给定这个数据:

user_id: 5, start_time: 2013-09-04 16:00:00 user_id:5,开始时间:2013-09-04 17:00:00 user_id: 5, start_time: 2013-09-04 18:00:00 user_id:6,开始时间:2013-09-04 16:00:00 user_id:6,开始时间:2013-09-04 17:00:00 user_id:6,开始时间:2013-09-04 18:00:00 user_id:6,开始时间:2013-09-04 20:00:00 user_id:6,开始时间:2013-09-04 21:00:00 user_id:6,开始时间: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

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

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

【问题讨论】:

【参考方案1】:

取决于您的数据库...窗口函数可以实现这一点。生成一个表示与前一列的增量的列(因此您需要它按 user_id、startTime 排序);然后,您可以使用该增量列进行分组。由于连续块在增量中将由“1”表示,并且新块的编号会更高。

您也可以通过使用子选择进行连接并将其偏移 1 来实现这一点,例如加入 ROW_NUMBER 和 ROW_NUMBER-1,然后您可以计算时间戳之间的增量,并使用外部选择进行一些魔术以得到你想要的。关键是增量。

你可以这样做:

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

【讨论】:

mysql 中不存在窗口函数。 使用 RANK 和 RANK-1 或 user_id 进行自我加入,开始时间 - 1 小时作为键会给你同样的能力 顺便说一句,InfiniDB 是一个与 MySQL 兼容的数据库(开源 GPLv2),具有建立在标准 MySQL 函数之上的窗口函数。 看起来 MySQL 可以被骗成 RANK。请参阅sqlfiddle.com/#!2/26e10/9 然后您可以使用自联接而不是我建议的半联接。不知道过程语言处理是否会带走我期望自加入的速度提升。不想引发数据库大战,但这确实是 MySQL 的不足。 与MySQL中计算排名的方法相同,可以计算增量【参考方案2】:

我的第一个建议是更改架构为 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) 上的索引。

【讨论】:

以上是关于如何检测 MySQL DATETIME 列中的连续小时数?的主要内容,如果未能解决你的问题,请参考以下文章

如何将两列中的日期和时间放入pandas to_datetime并设置为索引[重复]

MYSQL 中的日期格式不正确

如何在 Presto 中获取连续日期,其中一列中的开始日期和另一列中的结束日期

如何检查表列中的三个日期是不是连续月份

如何使用 PHP 为 MySQL DATETIME 类型生成日期?

从 SQL db 中连续检测 2 个重复值,否则更新插入(使用 python)