我们有一个这样的表:
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中完成吗?
答案 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)
上的索引。