4

租赁系统使用预订表来存储所有预订和预订:

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 中实现,而无需遍历每个预订及其继任者?

4

6 回答 6

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

于 2013-10-22T23:11:40.060 回答
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 中,外部查询找到所需的行。

也就是说,如果数据库结构与给定的一样,我不会在 SQL 中执行此操作。您可以通过在内部查询中使用一些 smort 来减少迭代次数WHERE到一个合理的时间跨度,但很可能,这不会很好地执行。

编辑

一个警告:我没有检查,但我认为,如果列表中没有事先预订,这将不起作用 - 这应该不是问题,因为在这种情况下,您的第一次预订尝试(原始时间)将起作用。

编辑

SQLfiddle

于 2013-10-22T22:48:59.967 回答
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

因此,如果您正在寻找最早的十二小时时段的开始时间和结束时间,您可以使用该结果集来执行此操作: 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
于 2013-10-22T22:50:39.057 回答
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

于 2013-10-22T23:01:12.073 回答
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
于 2013-10-22T22:39:55.940 回答
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 分钟的时间段可用。

一个问题可能是,如果没有可用的句点,则查询不会返回任何内容——那么您需要另一个查询来获取最远的enddate. 当然,您可以使用UNIONand来执行此操作LIMIT 1,但我认为最好仅按需运行“恢复”查询,以编程方式(即if empty(query) then new_query...)。

此外,在内部,WHERE您应该添加一个检查NOW()以避免过去的日期。如果将过期的预订移至非活动存储,这可能是不必要的。

于 2013-10-22T23:52:09.733 回答