MySQL:匹配两个日期之间有x个连续日期的记录

Kri*_*ris 11 mysql date date-range

背景/应用

我有一个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;
Run Code Online (Sandbox Code Playgroud)

样本数据

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');
Run Code Online (Sandbox Code Playgroud)

示例场景

用户选择他们想要在2011-03-10开始他们的住宿,他们希望逗留7天,他们有+/- 2天的灵活性.我编写了一个可视化下面数据和参数的图像.(红色:预订1,绿色:预订2,条纹:预订3,蓝色:日期范围(2011-03-10,+ 7天和+/- 2天灵活性))

预期结果

物业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)))));
Run Code Online (Sandbox Code Playgroud)

在样本数据集上,它相当快,但是在更大的数据集上,它会变得非常缓慢,甚至在构建完整的+/- 7天灵活性时更是如此.

有没有人对如何更好地编写这个查询有任何建议?

Dam*_*amp 2

好吧,这是一个棘手问题的棘手答案......

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;
Run Code Online (Sandbox Code Playgroud)

子查询选择所有不符合条件的属性。带有 IS NULL 的 LEFT JOIN 基本上可以解决排除问题(对不合格属性的否定)

  • 20110308 是所需的开始日期 -2 天(因为 +/-2 天的灵活性)
  • 20110319 是所需的结束日期 +2 天
  • 中的数字 4 是HAVING LEAST(startblock,endblock) > 4+/- 数字的两倍 (2*2)

我花了一段时间才解决这个问题(但你的问题很有趣,而且我有时间)

我已经用边缘情况对其进行了测试,并且它适用于我扔给它的所有测试用例......)。它背后的逻辑有点奇怪,但是一支很好的旧笔和纸帮助我解决了这个问题!

编辑

不幸的是,我意识到这适用于大多数情况,但并非全部......(在查找期开始和结束时的 2 个单日预订会导致该房产不可用,即使它应该可用)。

这里的问题是您必须查找数据库中不“存在”的信息并根据您拥有的数据重建它。查看我对您的问题的评论,以找到更好的方法来处理问题