租赁系统使用预订表存储所有预订和预订:
booking | item | startdate | enddate
1 | 42 | 2013-10-25 16:00 | 2013-10-27 12:00
2 | 42 | 2013-10-27 14:00 | 2013-10-28 18:00
3 | 42 | 2013-10-30 09:00 | 2013-11-01 09:00
…
假设用户想要从2013-10-27 12:00直到2013-10-28 12:00租用项目42,这是一天的时间段。系统将告诉他,该项目在给定的时间范围内不可用,因为预订号码。 2碰撞。
现在,我想建议所选项目再次可用的最早租赁日期和时间。当然,考虑用户所要求的期限(1天),从用户所需的日期和时间开始。
所以在上面的例子中,我正在寻找一个返回2013-10-28 18:00的SQL查询,自2013-10-27 12:00以来最早的日期,其中第42项可用于1当天,是2013-10-28 18:00至2013-10-29 18:00。
因此,我需要找到预订之间的差距,这个差距足以容纳用户的预订,并且与预期的开始日期尽可能接近。
或换句话说:我需要找到给定商品的第一个预订,之后有足够的空闲时间来预订用户。
这是否可以在普通SQL中进行,而不必遍历每个预订及其后继者?
答案 0 :(得分:3)
如果您无法重新设计数据库以使用更高效的内容,那么这将得到答案。你显然想要参数化它。它说要找到所需的日期,或者最早的结束日期,即租用间隔与现有预订不重叠:
Select
min(startdate)
From (
select
cast('2013-10-27 12:00' as datetime) startdate
from
dual
union all
select
enddate
from
booking
where
enddate > cast('2013-10-27 12:00' as datetime) and
item = 42
) b1
Where
not exists (
select
'x'
from
booking b2
where
item = 42 and
b1.startdate < b2.enddate and
b2.startdate < date_add(b1.startdate, interval 24 hour)
);
<强> Example Fiddle 强>
答案 1 :(得分:2)
SELECT startfree,secondsfree FROM (
SELECT
@lastenddate AS startfree,
UNIX_TIMESTAMP(startdate)-UNIX_TIMESTAMP(@lastenddate) AS secondsfree,
@lastenddate:=enddate AS ignoreme
FROM
(SELECT startdate,enddate FROM bookings WHERE item=42) AS schedule,
(SELECT @lastenddate:=NOW()) AS init
ORDER BY startdate
) AS baseview
WHERE startfree>='2013-10-27 12:00:00'
AND secondsfree>=86400
ORDER BY startfree
LIMIT 1
;
一些解释:内部查询使用变量将迭代移动到SQL中,外部查询找到所需的行。
也就是说,如果DB结构与给定的结构相似,我不会在SQL中执行此操作。您可以通过在内部查询中使用一些smort WHERE
来减少迭代次数,以达到理智的时间跨度,但很可能,这不会很好。
修改强>
警告:我没有检查,但我认为,如果列表中没有事先预约,这将不起作用 - 这应该不是问题,因为在这种情况下您的第一次预订尝试(原始时间)会工作的。
修改强>
答案 2 :(得分:1)
搜索重叠日期范围通常会导致SQL性能不佳。因此,拥有可用插槽的“日历”通常可以提高效率。
例如,预订2013-10-25 16:00 => 2013-10-27 12:00
实际上将由44条记录代表,每条记录一小时。
在下一次预订2013-10-27 14:00
之前的“差距”将由2条记录表示,每条记录一个小时。
然后,每个记录也可以具有持续时间(时间或插槽数),直到下一次更改。
slot_start_time | booking | item | remaining_duration
------------------+---------+------+--------------------
2013-10-27 10:00 | 1 | 42 | 2
2013-10-27 11:00 | 1 | 42 | 1
2013-10-27 12:00 | NULL | 42 | 2
2013-10-27 13:00 | NULL | 42 | 1
2013-10-27 14:00 | 2 | 42 | 28
2013-10-27 15:00 | 2 | 42 | 27
... | ... | ... | ...
2013-10-28 17:00 | 2 | 42 | 1
2013-10-28 18:00 | NULL | 42 | 39
2013-10-28 19:00 | NULL | 42 | 38
然后您的查询变为:
SELECT
*
FROM
slots
WHERE
slot_start_time >= '2013-10-27 12:00'
AND remaining_duration >= 24
AND booking IS NULL
ORDER BY
slot_start_time ASC
LIMIT
1
答案 3 :(得分:1)
好的,这在MySQL中并不常见。那是因为我们必须在子查询中伪造rownum值。
基本方法是将预订表的适当子集加入到自身偏移一个。
这是按预订时间排序的第42项预订的基本清单。我们无法通过booking_id订购,因为这些都不能保证按预订时间顺序排列。 (您是否尝试在两个现有预留之间插入新预订,嗯?)http://sqlfiddle.com/#!2/62383/9/0
SELECT @aserial := @aserial+1 AS rownum,
booking.*
FROM booking,
(SELECT @aserial:= 0) AS q
WHERE item = 42
ORDER BY startdate, enddate
这个子集加入了自身。诀窍是a.rownum+1 = b.rownum
,它将每一行连接到预订表子集中紧跟其后的那一行。 http://sqlfiddle.com/#!2/62383/8/0
SELECT a.booking_id, a.startdate asta, a.enddate aend,
b.startdate bsta, b.enddate bend
FROM (
SELECT @aserial := @aserial+1 AS rownum,
booking.*
FROM booking,
(SELECT @aserial:= 0) AS q
WHERE item = 42
ORDER BY startdate, enddate
) AS a
JOIN (
SELECT @bserial := @bserial+1 AS rownum,
booking.*
FROM booking,
(SELECT @bserial:= 0) AS q
WHERE item = 42
ORDER BY startdate, enddate
) AS b ON a.rownum+1 = b.rownum
这里再次显示每个预订(最后一个除外)和其后的小时数。 http://sqlfiddle.com/#!2/62383/15/0
SELECT a.booking_id, a.startdate, a.enddate,
TIMESTAMPDIFF(HOUR, a.enddate, b.startdate) gaphours
FROM (
SELECT @aserial := @aserial+1 AS rownum,
booking.*
FROM booking,
(SELECT @aserial:= 0) AS q
WHERE item = 42
ORDER BY startdate, enddate
) AS a
JOIN (
SELECT @bserial := @bserial+1 AS rownum,
booking.*
FROM booking,
(SELECT @bserial:= 0) AS q
WHERE item = 42
ORDER BY startdate, enddate
) AS b ON a.rownum+1 = b.rownum
因此,如果您要查找最早的12小时广告位的开始时间和结束时间,则可以使用该结果集执行此操作:http://sqlfiddle.com/#!2/62383/18/0
SELECT MIN(enddate) startdate, MIN(enddate) + INTERVAL 12 HOUR as enddate
FROM (
SELECT a.booking_id, a.startdate, a.enddate,
TIMESTAMPDIFF(HOUR, a.enddate, b.startdate) gaphours
FROM (
SELECT @aserial := @aserial+1 AS rownum,
booking.*
FROM booking,
(SELECT @aserial:= 0) AS q
WHERE item = 42
ORDER BY startdate, enddate
) AS a
JOIN (
SELECT @bserial := @bserial+1 AS rownum,
booking.*
FROM booking,
(SELECT @bserial:= 0) AS q
WHERE item = 42
ORDER BY startdate, enddate
) AS b ON a.rownum+1 = b.rownum
) AS gaps
WHERE gaphours >= 12
答案 4 :(得分:1)
这是查询,它将返回所需的日期,明显的条件 - 表格中应该有一些预订,但正如我从问题中看到的 - 你做了这个检查:
SELECT min(enddate)
FROM
(
select a.enddate from table4 as a
where
a.item=42
and
DATE_ADD(a.enddate, INTERVAL 1 day) <= ifnull(
(select min(b.startdate)
from table4 as b where b.startdate>=a.enddate and a.item=b.item),
a.enddate)
and
a.enddate>=now()
union all
select greatest(ifnull(max(enddate), now()),now()) from table4
) as q
您将更改INTERVAL 1 day
更改为INTERVAL ### hour
答案 5 :(得分:1)
如果我已正确理解您的要求,您可以尝试自我加入book
,以获得“空”空格,然后适合。这只是MySQL(我相信它可以适应其他人 - 当然是PostgreSQL):
SELECT book.*, TIMESTAMPDIFF(MINUTE, book.enddate, book.best) AS width FROM
(
SELECT book.*, MIN(book1.startdate) AS best
FROM book
JOIN book AS book1 USING (item)
WHERE item = 42 AND book1.startdate >= book.enddate
GROUP BY book.booking
) AS book HAVING width > 110 ORDER BY startdate LIMIT 1;
在上面的例子中,“110”是寻找的最小宽度,以分钟为单位。
同样的事情,可读性稍差(对我来说),删除了一个SELECT(非常快的SELECT,所以没什么优势):
SELECT book.*, MIN(book1.startdate) AS best
FROM book
JOIN book AS book1 ON (book.item = book1.item AND book.item = 42)
WHERE book1.startdate >= book.enddate
GROUP BY book.booking
HAVING TIMESTAMPDIFF(MINUTE, book.enddate, best) > 110
ORDER BY startdate LIMIT 1;
在你的情况下,一天是1440分钟
SELECT book.*, MIN(book1.startdate) AS best FROM book JOIN book AS book1 ON (book.item = book1.item AND book.item = 42) WHERE book1.startdate >= book.enddate GROUP BY book.booking HAVING TIMESTAMPDIFF(MINUTE, book.enddate, best) >= 1440 ORDER BY startdate LIMIT 1;
+---------+------+---------------------+---------------------+---------------------+
| booking | item | startdate | enddate | best |
+---------+------+---------------------+---------------------+---------------------+
| 2 | 42 | 2013-10-27 14:00:00 | 2013-10-28 18:00:00 | 2013-10-30 09:00:00 |
+---------+------+---------------------+---------------------+---------------------+
1 row in set (0.00 sec)
...返回的时间段为2,即在预订2结束时,直到预订3的“最佳”,至少有1440分钟的时间段。
问题可能是,如果没有期间可用,查询会返回 nothing - 那么您需要另一个查询来获取最远的enddate
。您可以使用UNION
和LIMIT 1
来执行此操作,但我认为最好只按需编程运行“恢复”查询(即if empty(query) then new_query...
)。
此外,在内部WHERE
中,您应添加NOW()
的检查,以避免过去的日期。如果过期预订被移至非活动存储,则可能没有必要。