PHP / MYSQL日期时间范围与用户重叠

时间:2012-04-01 10:07:40

标签: php mysql datetime

请为此我需要帮助(为了更好地理解,请参见附图),因为我完全无助。

http://img16.imageshack.us/img16/7196/overlapsen.jpg http://img16.imageshack.us/img16/7196/overlapsen.jpg

正如您所看到的,我有用户,他们将我们的开始和结束日期时间存储在我的数据库中,如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

3 个答案:

答案 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 BYGROUP_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。