请为此我需要帮助(为了更好地理解,请参见附图),因为我完全无助。
正如您所看到的,我有用户,他们将我们的开始和结束日期时间存储在我的数据库中,如YYYY-mm-dd H:i:s。现在我需要根据最常见的时间范围重叠(对于大多数用户)找出所有用户的重叠。我想为大多数用户提供3个最常访问的数据时间重叠。我该怎么做?
我不知道我应该使用哪个mysql查询,或者最好从数据库中选择所有日期时间(开始和结束)并在php中处理它(但是如何?)。如图所示,结果应该是例如时间8.30 - 10.00是用户A + B + C + D的结果。
Table structure:
UserID | Start datetime | End datetime
--------------------------------------
A | 2012-04-03 4:00:00 | 2012-04-03 10:00:00
A | 2012-04-03 16:00:00 | 2012-04-03 20:00:00
B | 2012-04-03 8:30:00 | 2012-04-03 14:00:00
B | 2012-04-06 21:30:00 | 2012-04-06 23:00:00
C | 2012-04-03 12:00:00 | 2012-04-03 13:00:00
D | 2012-04-01 01:00:01 | 2012-04-05 12:00:59
E | 2012-04-03 8:30:00 | 2012-04-03 11:00:00
E | 2012-04-03 21:00:00 | 2012-04-03 23:00:00
答案 0 :(得分:2)
你实际拥有的是一组集合,并希望确定它们中是否有任何非交叉点。这是在尝试查找嵌套集中节点的所有祖先时要求的确切问题。
我们可以证明,对于每个重叠,至少有一个时间窗口的开始时间落在所有其他重叠时间窗口内。使用这个小窍门,我们不需要在当天真正构建人工时段。只需要一个开始时间,看看它是否与任何其他时间窗口相交,然后只计算交叉点的数量。
那么查询是什么?
/*SELECT*/
SELECT DISTINCT
MAX(overlapping_windows.start_time) AS overlap_start_time,
MIN(overlapping_windows.end_time) AS overlap_end_time ,
(COUNT(overlapping_windows.id) - 1) AS num_overlaps
FROM user_times AS windows
INNER JOIN user_times AS overlapping_windows
ON windows.start_time BETWEEN overlapping_windows.start_time AND overlapping_windows.end_time
GROUP BY windows.id
ORDER BY num_overlaps DESC;
根据您的表格大小以及您计划运行此查询的频率,可能值得在其上删除空间索引(请参阅下文)。
如果您经常运行此查询,则需要使用空间索引。由于基于范围的遍历(即start_time落在开始/结束范围之间),BTREE索引不会为您做任何事情。它应该是空间的。
ALTER TABLE user_times ADD COLUMN time_windows GEOMETRY NOT NULL DEFAULT 0;
UPDATE user_times SET time_windows = GeomFromText(CONCAT('LineString( -1 ', start_time, ', 1 ', end_time, ')'));
CREATE SPATIAL INDEX time_window ON user_times (time_window);
然后您可以更新上述查询中的ON子句以读取
ON MBRWithin( Point(0,windows.start_time), overlapping_windows.time_window )
这将为您提供查询的索引遍历。如果您经常计划运行查询,请再次执行此操作。
将空间索引归功于Quassoni's blog。
答案 1 :(得分:0)
这样的事情应该让你开始 -
SELECT slots.time_slot, COUNT(*) AS num_users, GROUP_CONCAT(DISTINCT user_bookings.user_id ORDER BY user_bookings.user_id) AS user_list
FROM (
SELECT CURRENT_DATE + INTERVAL ((id-1)*30) MINUTE AS time_slot
FROM dummy
WHERE id BETWEEN 1 AND 48
) AS slots
LEFT JOIN user_bookings
ON slots.time_slot BETWEEN `user_bookings`.`start` AND `user_bookings`.`end`
GROUP BY slots.time_slot
ORDER BY num_users DESC
我们的想法是创建一个由当天时间段组成的派生表。在这个例子中,我使用dummy(可以是任何具有所需集合的AI id的表)来通过逐步添加30分钟来创建时隙列表。然后将其结果与预订相结合,以便能够计算每个时段的书籍数量。
更新对于整个日期/时间范围,您可以使用此类查询来获取所需的其他数据 -
SELECT MIN(`start`) AS `min_start`, MAX(`end`) AS `max_end`, DATEDIFF(MAX(`end`), MIN(`start`)) + 1 AS `num_days`
FROM user_bookings
然后可以将这些值替换为原始查询,或者可以将这两个值组合起来 -
SELECT slots.time_slot, COUNT(*) AS num_users, GROUP_CONCAT(DISTINCT user_bookings.user_id ORDER BY user_bookings.user_id) AS user_list
FROM (
SELECT DATE(tmp.min_start) + INTERVAL ((id-1)*30) MINUTE AS time_slot
FROM dummy
INNER JOIN (
SELECT MIN(`start`) AS `min_start`, MAX(`end`) AS `max_end`, DATEDIFF(MAX(`end`), MIN(`start`)) + 1 AS `num_days`
FROM user_bookings
) AS tmp
WHERE dummy.id BETWEEN 1 AND (48 * tmp.num_days)
) AS slots
LEFT JOIN user_bookings
ON slots.time_slot BETWEEN `user_bookings`.`start` AND `user_bookings`.`end`
GROUP BY slots.time_slot
ORDER BY num_users DESC
编辑我在DISTINCT
中添加了ORDER BY
和GROUP_CONCAT()
条款,以回应您的上一次查询。
请注意,在虚拟表中您将需要更大范围的ID。我没有测试过这个查询,因此它可能有语法错误。
答案 2 :(得分:0)
表似乎很简单。我会保持你的SQL查询非常简单:
SELECT * FROM tablename
然后,当您在PHP对象中保存信息时。使用循环和比较使用PHP进行处理。
最简单的形式:
for($x, $numrows = mysql_num_rows($query); $x < $numrows; $x++){
/*Grab a row*/
$row = mysql_fetch_assoc($query);
/*store userID, START, END*/
$userID = $row['userID'];
$start = $row['START'];
$end = $row['END'];
/*Have an array for each user in which you store start and end times*/
if(!strcmp($userID, "A")
{
/*Store info in array_a*/
}
else if(!strcmp($userID, "B")
{
/*etc......*/
}
}
/*Now you have an array for each user with their start/stop times*/
/*Do your loops and comparisons to find common time slots. */
/*Also, use strtotime() to switch date/time entries into comparable values*/
当然这是非常基本的形式。在上面显示的循环中比较它们之前,您可能希望在数组中进行一次循环以首先获取所有userID。