MySQL选择日期不在日期之间的行

时间:2010-11-12 14:33:29

标签: date mysql

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

table: room
columns: id, maxGuests

table: roombooking
columns: id, startDate, endDate

table: roombooking_room:
columns: id, room_id, roombooking_id

我需要选择适合所请求客人的房间,或选择两个(或更多)房间以适应客人(由maxGuests定义,显然首先使用最低/壁橱maxGuest)

我可以遍历我的日期范围并使用这个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是要预订的客人数量。但如果客人比任何房间都多,那么这将返回false,并且必须有更快的方式来执行此操作而不是使用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'];
        }

3 个答案:

答案 0 :(得分:5)

假设您有兴趣将@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负责处理NULL - 如果房间没有预订,它将没有任何记录与日期进行比较,因此它不会返回完全免费的房间。如果date1或date2为null,则date1和date2之间的日期将返回NULL,并且coalesce将其变为true(替代方法是执行完全空闲房间的UNION;这可能更快)。

对于多个房间,事情变得非常有趣。 这种情况是你问题的重要组成部分吗?您使用的是哪个数据库,即您是否可以访问递归查询?

修改

正如我之前多次说过的那样,如果你想在所需的客人数和客房数之间达到最佳匹配,那么寻找解决方案的方式(首先查看最大免费房间的贪婪算法)并不是最佳选择。 / p>

所以,如果你用

替换你的foreach
$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);

将通过所有可能的组合并找到浪费最少空格的解决方案。

答案 1 :(得分: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`

但是,有一种更简单的说法。范围在另一个范围之外的唯一方法是start_date在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

还有其他方法可以做到这一点,所以继续寻找......

答案 2 :(得分: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

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

在最坏的情况下,使用合适的索引,应扫描一半的预订。