【问题标题】:MySQL select rows where date not between dateMySQL选择日期不在日期之间的行
【发布时间】:2010-11-12 14:33:29
【问题描述】:

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

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 是要预订的客人数量。但是如果客人数量超过任何房间可以容纳的人数,这将返回 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'];
        }

【问题讨论】:

  • 为什么有单独的 roombooking_room 表? table roombooking: id, room_id, startDate, endDate 不应该足够吗?
  • 我认为执行您想要的操作所需的 SQL 实际上对于您想要实现的目标来说过于复杂。循环和使用 PHP 有什么问题?您可能还会发现,如果您使用纯 SQL 实现所需的结果,该解决方案实际上可能比使用 PHP 循环慢。但是,我对查看结果非常感兴趣,因为有时我发现自己会问类似的问题(PHP 与 SQL)。
  • @Axaryday 请参阅下面对答案的评论。这是必需的,因为一个预订期可能关联多个房间。也就是我住10个人,一个房间可以住6个人,所以我需要两个房间,但是在同一个订单下
  • 我可能误解了这个问题。我的印象是您希望检查一系列任意日期(例如,8 月的每个周末),而不是“8 月 1 日至 14 日之间”。如果是这种情况,我们深表歉意。

标签: date mysql


【解决方案1】:

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

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

编辑

正如我之前多次说过的,如果您想在所需的客人数量和房间之间获得最佳匹配,那么您寻找解决方案的方式(首先查看最大空闲房间的贪心算法)并不是最佳选择。

所以,如果您将 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);

将遍历所有可能的组合并找到浪费最少空间的解决方案。

【讨论】:

  • 谢谢。对于多个房间来说,这不是必须的——我总是可以硬编码多个房间的情况——但那似乎放弃了
  • @Ashley,多个房间的问题是您必须检查所有可能的房间组合以找到最佳解决方案 (2^n-1)。您通常可以拥有多少个房间,又有多少个大小相同?
  • 对于这个网站,只有 14 个房间,6 到 10 人之间。但你是对的,这可能会改变其他客户并可能导致问题。 ircmaxwell 是一个很好的打击。也许我会继续我的想法,让房间有 maxGuests 并循环,直到没有更多的客人可以分配。
  • @Ashley,正如我之前所说,循环在找到最佳解决方案时会遇到问题。如果您有 n 行可能是最佳解决方案的一部分,那么您必须检查 2^n-1 个组合。这不是特别适合 SQL(尤其是没有递归;你可以选择 postgres 吗?)。或者,如果您不能使用递归,您可以有一个列出所有组合的辅助表,但即使有 14 个房间(如果要寻找精确的最佳值),您也必须在最坏的情况下检查 2^14-1,即大约是 ~2*10^5 行。
【解决方案2】:

好的,首先,您使用的内部查询是笛卡尔连接,并且会非常昂贵。您需要指定加入条件(例如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

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

【讨论】:

  • 谢谢,我认为这是前进的方向,当 maxGuests 小于或等于请求的客人数量时,这将非常有效。我想我将不得不运行这个,如果它不成功,那么得到 maxGuests 的房间并从总客人中减去这个房间,然后再次运行这个。有循环的循环,但我认为这是唯一的方法?
  • @Ashley,实际上没有,而且您的建议并不详尽 - 您可能会错过一个好的解决方案。假设您在一段时间内有 3 个免费房间,一个有 10 个空间,两个有 7 个空间,并且您想要容纳 14 人。使用贪心算法,您将占用 10 和 7 的房间,而错过了两个有 7 个空间的房间的解决方案。
  • 所以循环遍历似乎是一个好方法。用他们的壁橱 maxGuests 组织房间(我认为 noGuests >= maxGuests ORDER BY maxGuests 限制为 1)?
【解决方案3】:
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

我可能会想念左连接是如何工作的,因此您可能需要在拥有时使用稍微不同的条件,可能是计数或总和。

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

【讨论】:

  • 如果您不需要聚合,通常不使用 GROPY BY,并且在上述情况下您没有使用任何聚合 - 因此您可以在 rooms.id 上使用 DISTINCT 踢出 GROUP BY 并移动 HAVING到 WHERE(即使您确实有聚合/需要分组,您也应该将条件移动到 where 部分;具有用于聚合上的条件并打算应用于结果集之后聚合计算)
  • @Unreason:虽然这对于IS NULL 版本可能有效(或者甚至是必要的),但对于sumcount 版本则完全相反。对于他们来说,过滤器必须应用于聚合结果,因此我使用 HAVING 而不是 WHERE 子句的原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-22
  • 1970-01-01
  • 2014-02-24
  • 2023-03-02
  • 2023-01-14
相关资源
最近更新 更多