背景/应用
我有一个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天灵活性))
预期结果
属性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天灵活性时更是如此。
有没有人对如何更好地编写此查询有任何建议?
答案 0 :(得分: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;
子查询选择不符合条件的所有属性。带有IS NULL的LEFT JOIN基本上排除了排除(对不合格属性的否定)
HAVING LEAST(startblock,endblock) > 4
中的数字4是+/-数字的两倍(2 * 2)花了我一段时间来解决它(但你的问题很有趣,我有时间在我的手上)
我用边缘情况对它进行了测试,它适用于我抛出的所有测试用例......)。它背后的逻辑有点奇怪,但一个好的旧笔和纸帮我解决了!
修改
不幸的是,我意识到这对大多数情况都有效,但不是全部...(在查询期开始和结束时进行的2次单日预订会使财产无法使用,即使它应该可用)。
这里的问题是你必须查找数据库中没有“存在”的信息,并根据你拥有的数据重建它。查看我对您的问题的评论,以便找到解决问题的更好方法
答案 1 :(得分:0)
我认为这就是你要找的东西:
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查询应该比多个子查询更有效地覆盖它。