MySQL: совпадающие записи, у которых x последовательных дат доступны между двумя датами - PullRequest
11 голосов
/ 17 февраля 2011

Справочная информация / Приложение

У меня есть база данных MySQL, содержащая таблицу арендуемых свойств и таблицу бронирований для этих свойств. Существует также функция поиска для поиска доступных свойств между двумя указанными датами. При поиске пользователь может ввести дату начала, количество дней, которые он хочет остаться, и гибкость дат до +/- 7 дней. Бронирование может начаться в тот же день, когда заканчивается другое бронирование (вечеринка 1 отправляется утром, вечеринка 2 - вечером).

У меня возникли проблемы с эффективным внедрением функции гибкости.

Схема

CREATE TABLE IF NOT EXISTS `property` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `name` varchar(60) COLLATE utf8_unicode_ci DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE IF NOT EXISTS `property_booking` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `property_id` bigint(20) DEFAULT NULL,
    `name` varchar(60) COLLATE utf8_unicode_ci DEFAULT NULL,
    `date_start` date DEFAULT NULL,
    `date_end` date DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Пример данных

INSERT INTO `property` (`name`) 
VALUES ('Property 1'), ('Property 2'), ('Property 3');

INSERT INTO `property_booking` (`property_id`,`name`,`date_start`,`date_end`) 
VALUES (1, 'Steve', '2011-03-01', '2011-03-08'), 
(2, 'Bob', '2011-03-13', '2011-03-20'), 
(3, 'Jim', '2011-03-16', '2011-03-23');

Пример сценария

Пользователь выбирает, что он хочет начать свое пребывание в 2011-03-10, он хочет остаться в течение 7 дней, и у него есть гибкость +/- 2 дня. Я собрал изображение, которое визуализирует данные и параметры ниже. (красный: бронирование 1, зеленый: бронирование 2, полосы: бронирование 3, синий: диапазон дат (2011-03-10, + 7 дней и +/- 2 дня гибкости))

image

Ожидаемый результат

Недвижимость 1 (бронирование доступно в диапазоне дат)
Недвижимость 3 (бронирование доступно начиная с 2011-03-08 или 2011-03-09)

Текущий метод

Мой текущий запрос проверяет перекрытие для всех 7-дневных диапазонов дат в общем диапазоне дат с возможностью поиска, например:

SELECT p.`id`, p.`name` 
FROM `property` p 
WHERE (NOT (EXISTS (SELECT p2.`name` FROM `property_booking` p2 WHERE (p2.`property_id` = p.`id` AND '2011-03-10' < DATE_SUB(p2.`date_end`, INTERVAL 1 DAY) AND '2011-03-17' > DATE_ADD(p2.`date_start`, INTERVAL 1 DAY))))) 
OR (NOT (EXISTS (SELECT p3.`name` FROM `property_booking` p3 WHERE (p3.`property_id` = p.`id` AND '2011-03-11' < DATE_SUB(p3.`date_end`, INTERVAL 1 DAY) AND '2011-03-18' > DATE_ADD(p3.`date_start`, INTERVAL 1 DAY))))) 
OR (NOT (EXISTS (SELECT p4.`name` FROM `property_booking` p4 WHERE (p4.`property_id` = p.`id` AND '2011-03-09' < DATE_SUB(p4.`date_end`, INTERVAL 1 DAY) AND '2011-03-16' > DATE_ADD(p4.`date_start`, INTERVAL 1 DAY))))) 
OR (NOT (EXISTS (SELECT p5.`name` FROM `property_booking` p5 WHERE (p5.`property_id` = p.`id` AND '2011-03-12' < DATE_SUB(p5.`date_end`, INTERVAL 1 DAY) AND '2011-03-19' > DATE_ADD(p5.`date_start`, INTERVAL 1 DAY)))))
OR (NOT (EXISTS (SELECT p6.`name` FROM `property_booking` p6 WHERE (p6.`property_id` = p.`id` AND '2011-03-08' < DATE_SUB(p6.`date_end`, INTERVAL 1 DAY) AND '2011-03-15' > DATE_ADD(p6.`date_start`, INTERVAL 1 DAY)))));

В примере набора данных он достаточно быстрый, но в гораздо больших наборах данных он будет довольно вялым, даже более того, когда вы создадите полную гибкость +/- 7 дней.

У кого-нибудь есть предложения относительно того, как лучше написать этот запрос?

Ответы [ 2 ]

2 голосов
/ 18 февраля 2011

Хорошо, вот хитрый ответ на сложный вопрос ...

SELECT * FROM property AS p
LEFT JOIN  
(
  SELECT property_id, DATEDIFF(MAX(date_end),20110308) AS startblock, 
      DATEDIFF(20110319,MIN(date_start))-1 AS endblock
  FROM property_booking AS pb
  WHERE date_start < 20110319 || date_end >= 20110308 
  GROUP BY property_id
  HAVING LEAST(startblock,endblock) > 4
) AS p2 ON p.id = p2.property_id 
WHERE p2.property_id IS NULL;

Подзапрос выбирает все свойства, которые не подходят.LEFT JOIN с IS NULL в основном работает для исключения (отрицание неподходящих свойств)

  • 20110308 - желаемая дата начала -2 дня (потому что +/- 2 дня гибкости)
  • 20110319 - желаемая дата окончания +2 дня
  • Число 4 в HAVING LEAST(startblock,endblock) > 4 вдвое больше вашего номера +/- (2 * 2)

Это заняло у меня некоторое времячтобы разобраться с этим (но ваш вопрос был интересным, и у меня было время под рукой)

Я протестировал его с крайними случаями, и он работал для всех тестовых случаев, которые я бросил в него ...),Логика немного странная, но старые добрые ручка и бумага помогли мне разобраться!

Edit

К сожалению, я понял, что это будет работать в большинстве случаевно не все ... (2 однодневных бронирования в самом начале и в конце периода поиска делают свойство недоступным, даже если оно должно быть доступно).

Проблема в том, что вам нужно искать информациюэто не «присутствует» в БД и восстановить его из того, что у вас есть данные.Прочтите мой комментарий на ваш вопрос, чтобы увидеть лучший способ решения проблемы

0 голосов
/ 18 февраля 2011

Я думаю, это то, что вы ищете:

   SELECT MAX( IF( (    b.date_start < '2011-03-08' + INTERVAL 7 DAY
                    AND b.date_end > '2011-03-08'), 1, 0)) AS is_booked,
          p.id,
          p.name
     FROM property p
LEFT JOIN property_booking b ON p.id = b.property_id
 GROUP BY p.id
   HAVING is_booked < 1

Если вы хотите включить свободу действий, разверните агрегат MAX (), чтобы включить опции:

   SELECT MAX( IF(    (    b.date_start < '2011-03-08' + INTERVAL 7 DAY
                       AND b.date_end > '2011-03-08')
                  AND (    b.date_start < '2011-03-08' + INTERVAL 7 DAY + INTERVAL 1 DAY
                       AND b.date_end > '2011-03-08' + INTERVAL 1 DAY)
                  AND (    b.date_start < '2011-03-08' + INTERVAL 7 DAY + INTERVAL 2 DAY
                       AND b.date_end > '2011-03-08' + INTERVAL 2 DAY), 1, 0)
             ) AS is_booked,
          p.id,
          p.name
     FROM property p
LEFT JOIN property_booking b ON p.id = b.property_id
 GROUP BY p.id
   HAVING is_booked < 1

Если я правильно понимаю ваш вопрос, этот запрос GROUP BY должен охватывать его более эффективно, чем несколько подзапросов.

...