2010-11-12 63 views
5

我有一个预订系统,我需要从数据库中选择任何可用的房间。基本设置是:MySQL选择日期不在日期之间的行

table: room 
columns: id, maxGuests 

table: roombooking 
columns: id, startDate, endDate 

table: roombooking_room: 
columns: id, room_id, roombooking_id 

,我需要选择的客房,可容纳请求的客人,或选择两个(或更多)的房间,以适应宾客(由maxGuests定义,显然用最低/壁橱maxGuests第一)

我可以遍历我的日期范围,并使用此SQL:

SELECT `id` 
FROM `room` 
WHERE `id` NOT IN 
(
    SELECT `roombooking_room`.`room_id` 
    FROM `roombooking_room`, `roombooking` 
    WHERE `roombooking`.`confirmed` =1 
    AND DATE(%s) BETWEEN `roombooking`.`startDate` AND `roombooking`.`endDate` 
) 
AND `room`.`maxGuests`>=%d 

其中,%$ 1是环状日期%2d是在被预订的客人数量,但这将只是如果有多于任何房间的客人可以返回假,并且必须有一个quic更好的方式做到这一点,而不是循环与PHP和运行查询?

这类似于SQL的一部分,我想的是:Getting Dates between a range of dates但与MySQL


解决方案的基础上,ircmaxwell的回答是:

$query = sprintf(
     "SELECT `id`, `maxGuests` 
     FROM `room` 
     WHERE `id` NOT IN 
     (
      SELECT `roombooking_room`.`room_id` 
      FROM `roombooking_room` 
      JOIN `roombooking` ON `roombooking_room`.`roombooking_id` = `roombooking`.`id` 
      WHERE `roombooking`.`confirmed` =1 
      AND (`roomBooking`.`startDate` > DATE(%s) OR `roomBooking`.`endDate` < DATE(%s)) 
     ) 
     AND `maxGuests` <= %d ORDER BY `maxGuests` DESC", 
     $endDate->toString('yyyy-MM-dd'), $startDate->toString('yyyy-MM-dd'), $noGuests); 
     $result = $db->query($query); 
     $result = $result->fetchAll(); 

     $rooms = array(); 
     $guests = 0; 
     foreach($result as $res) { 
      if($guests >= $noGuests) break; 
      $guests += (int)$res['maxGuests']; 
      $rooms[] = $res['id']; 
     } 
+0

为什么你有一个单独的roombooking_room表?不应该tablerooming:id,room_id,startDate,endDate是否够了? – Axarydax 2010-11-12 14:44:21

+0

我认为,为了实现目标,对于想要实现的目标而言,执行所需任务所需的SQL会过于复杂。循环和使用PHP有什么问题?您也可能会发现,如果您使用纯SQL实现期望的结果,则该解决方案实际上可能比用PHP循环更慢。但是,我对看到结果非常感兴趣,因为我有时会发现自己提出了一个类似的问题(PHP vs SQL)。 – 2010-11-12 15:01:22

+0

@Axaryday请在下面看到关于答案的评论。这是必要的,因为一个预订周期可能有多个房间关联。即,我住10人,一个房间可以带6人,因此我需要两个房间,但在相同的预订 – Ashley 2010-11-12 15:50:58

回答

4

假设你有兴趣的地方@Guests@StartDate@EndDate

SELECT DISTINCT r.id, 
FROM room r 
    LEFT JOIN roombooking_room rbr ON r.id = rbr.room_id 
    LEFT JOIN roombooking ON rbr.roombooking_id = rb.id 
WHERE COALESCE(@StartDate NOT BETWEEN rb.startDate AND rb.endDate, TRUE) 
     AND COALESCE(@EndDate NOT BETWEEN rb.startDate AND rb.endDate, TRUE) 
     AND @Guests < r.maxGuests 

应该给你的所有的客房,免费,可容纳客人给定数量的在一定期间的列表。

注意
此查询仅适用于单间,如果你想看看在多个房间,你需要同样的标准适用于房间的组合。为此,您需要递归查询或一些辅助表。 此外,COALESCE是否有照顾空 - 如果一个房间没有被预订,它将不会有任何记录与日期比较,所以它不会返回完全免费的房间。日期1和日期2之间的日期将返回NULL,如果date1或date2为空并且coalesce将其变为true(或者是完全免费房间的UNION;这可能会更快)。

与多个房间的事情变得非常有趣。 这是你的问题的大部分吗?你正在使用哪个数据库,即是否有权访问递归查询?

编辑

正如我前面所说多次,你的寻找一个解决方案(贪心算法,着眼于最大的免费客房第一)的方式并不是最佳的,如果你想获得之间的最佳契合所需客人数量和客房数量。

所以,如果你有

$bestCapacity = 0; 
$bestSolution = array(); 

for ($i = 1; $i <= pow(2,sizeof($result))-1; $i++) { 
    $solutionIdx = $i; 
    $solutionGuests = 0; 
    $solution = array(); 
    $j = 0; 
    while ($solutionIdx > 0) : 
     if ($solutionIdx % 2 == 1) { 
      $solution[] = $result[$j]['id']; 
      $solutionGuests += $result[$j]['maxGuests']; 
     } 
     $solutionIdx = intval($solutionIdx/2); 
     $j++; 
    endwhile;  
    if (($solutionGuests <= $bestCapacity || $bestCapacity == 0) && $solutionGuests >= $noGuests) { 
     $bestCapacity = $solutionGuests; 
     $bestSolution = $solution; 
    } 
} 

print_r($bestSolution); 
print_r($bestCapacity); 

取代你的foreach会经过所有可能的组合,发现浪费的空间数最少的解决方案。

+0

感谢您的支持。对多个房间来说这不是必须的 - 我总是可以对多个房间的情况进行硬编码 - 但是这似乎是放弃了 – Ashley 2010-11-12 19:09:20

+0

@Ashley,多个房间的问题是您必须检查所有可能的房间组合以找到最佳解决方案(2^N-1)。您通常可以拥有多少个房间,以及多少个房间的大小相同? – Unreason 2010-11-12 19:17:43

+0

对于本网站而言,只有14个房间的房间介于6和10之间。但是你说得对,这可能会改变其他客户,并可能导致问题。 ircmaxwell打出了很好的一击。也许我会跟着我的想法,让maxGuests获得房间并循环,直到没有更多的客人分配为止。 – Ashley 2010-11-12 19:20:17

3

好吧,首先,内部查询你使用的是笛卡尔连接,而且会非常昂贵。您需要指定加入标准(例如,roombooking_room.booking_id = roombooking.id)。其次,假设你有一个日期范围,我们可以说什么呢?那么,让我们称您的范围rangeStartDaterangeEndDate的开始。

现在,我们可以说关于任何其他范围的日期没有任何形式的重叠范围?那么,endDate一定不能在rangeStartDaterangeEndDate之间。与startDate一样。而rangeStartDate(和rangeEndDate,但我们并不需要检查它)不能startDateendDate之间...

因此,假设%1$srangeStartDate%2$srangeEndDate,全面的where子句可能是:

WHERE `roomBooking`.`startDate` NOT BETWEEN %1$s AND %2s 
    AND `roomBooking`.`endDate` NOT BETWEEN %1$s AND %2$$s 
    AND %1s NOT BETWEEN `roomBooking`.`startDate` AND `roomBooking`.`endDate` 

但是,有一种更简单的说法。一个范围是另一个之外的唯一方法是在起始日期是END_DATE后,或END_DATE是START_ID

所以之前,假设%1$srangeStartDate%2$srangeEndDate,另一个全面的where子句可能是:

WHERE `roomBooking`.`startDate` > %2$s 
    OR `roomBooking`.`endDate` < %1$s 

所以,这使你的整体查询:

SELECT `id` 
FROM `room` 
WHERE `id` NOT IN 
(
    SELECT `roombooking_room`.`room_id` 
    FROM `roombooking_room` 
    JOIN `roombooking` ON `roombooking_room`.`roombooking_id` = `roombooking`.`id` 
    WHERE `roombooking`.`confirmed` =1 
    AND (`roomBooking`.`startDate` > %2$s 
     OR `roomBooking`.`endDate` < %1$s) 
) 
AND `room`.`maxGuests`>=%d 

有这样做,以及其他方式,就这样一直看着......

+0

谢谢,我认为这是前进的方向,当maxGuests小于或等于请求的guest虚拟机数量时,这将工作得很好。我想我必须运行这个,如果它不成功,那么使用maxGuests获得房间,然后减去总客人的房间并再次运行。循环循环,但我认为这是唯一的方法? – Ashley 2010-11-12 15:54:06

+0

@Ashley,实际上不是,你提出的建议并不全面 - 你可能会错过一个很好的解决方案。考虑你有一段时间有3个免费房间,一个有10个空间,两个有7个,你想容纳14个人。使用贪婪算法,您将占用10和7的空间,并错过两个7个空间的解决方案。 – Unreason 2010-11-12 19:14:58

+0

因此,循环似乎是一种好方法。组织房间与他们的衣柜maxGuests(其中noGuests> = maxGuests ORDER BY maxGuests限制1)我认为? – Ashley 2010-11-12 19:21:36

0
SELECT rooms.id 
FROM rooms LEFT JOIN bookings 
ON booking.room_id = rooms.id 
WHERE <booking overlaps date range of interest> AND <wherever else> 
GROUP BY rooms.id 
HAVING booking.id IS NULL 

我可能会错过记住左连接如何工作,所以你可能需要使用稍有不同的条件,有可能是一个计数或总和。

最糟糕的是,如果有合适的索引,那么应该扫描一半的预订。

+0

如果您不需要聚合,您通常不使用GROPY BY,并且在上述情况下您不使用任何 - 因此,您可以在rooms.id上使用DISTINCT,并将HAVING移动到WHERE(您应该移动条件,即使你确实有聚合/需求组;根据聚合条件和打算应用于结果集*计算聚合后) – Unreason 2010-11-13 14:23:52

+0

@不理想:虽然这可能工作(或者甚至在第二个想法中是必要的)对于'IS NULL'版本来说,'sum'或'count'版本恰好相反。对于他们来说,过滤器必须适用于聚合结果,因此我使用“HAVING”而不是“WHERE”子句的原因。 – BCS 2010-11-15 15:12:37