MySQL выбрать строки, где дата не между датой - PullRequest
5 голосов
/ 12 ноября 2010

У меня есть система бронирования, в которой мне нужно выбрать любой доступный номер из базы данных. Базовая настройка:

table: room
columns: id, maxGuests

table: roombooking
columns: id, startDate, endDate

table: roombooking_room:
columns: id, room_id, roombooking_id

Мне нужно выбрать комнаты, в которых могут разместиться запрошенные гости, или выбрать две (или более) комнаты для гостей (в соответствии с определением maxGhest, очевидно, сначала используется самый низкий / закрытый maxGhest)

Я мог бы перебрать свой диапазон дат и использовать этот 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, о которой я думал: Получение дат между диапазонами дат , но с 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 ]

5 голосов
/ 12 ноября 2010

Предполагая, что вы заинтересованы в размещении @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 - если комната не забронирована вообще, у нее не будет записей с датами для сравнения, поэтому она не вернет полностью свободные комнаты.Date между date1 и date2 вернет NULL, если date1 или date2 будет нулевым, и coalesce превратит его в true (альтернатива - создать 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);

Пройдет все возможные комбинации и найдет решение, которое тратит наименьшее количество пробелов.

3 голосов
/ 12 ноября 2010

Хорошо, во-первых, используемый вами внутренний запрос является декартовым соединением и будет ОЧЕНЬ дорогим.Вам необходимо указать критерии объединения (например, roombooking_room.booking_id = roombooking.id).

Во-вторых, если у вас есть диапазон дат, что мы можем сказать по этому поводу?Ну, давайте назовем начало вашего диапазона rangeStartDate и rangeEndDate.

Теперь, что мы можем сказать о любом другом диапазоне дат, который не имеет формы перекрытия с этим диапазоном?Ну, endDate не должно быть между rangeStartDate и rangeEndDate.То же самое с startDaterangeStartDaterangeEndDate, но нам не нужно его проверять) не может быть между startDate и endDate ...

Итак, предположим, что %1$s равно rangeStartDateи %2$s - это rangeEndDate, всестороннее предложение 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$s равно rangeStartDate, а %2$s равно rangeEndDate, еще одно подробное предложение 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 голосов
/ 13 ноября 2010
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

Возможно, я не помню, как работает левое объединение, поэтому вам может потребоваться использовать несколько иное условие для наличия, может быть, счет или сумма.

В худшем случае, с подходящими индексами, чтоследует отсканировать половину заказов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...