查找可用间隔

时间:2014-07-24 09:35:30

标签: sql

我有两张名为EntryBooking的牌桌。我的想法是:我创建一个entry,其中startend时间,然后我可以为此entry创建预订。 bookingentry相关联,并且还有startendbooking位于entry的时间内,意为booking.start >= entry.startbooking.end <= entry.end。此外,一个bookings

中没有重叠entry

&#34;条目&#34;表

id |       start         |           end
----------------------------------------------
1  | 2014-07-24 09:00:00 | 2014-07-24 11:20:00

&#34;预订&#34;表

id |       start         |           end       | entry_id
-----------------------------------------------------------
1  | 2014-07-24 09:10:00 | 2014-07-24 09:40:00 |     1
2  | 2014-07-24 09:50:00 | 2014-07-24 10:20:00 |     1
3  | 2014-07-24 10:50:00 | 2014-07-24 11:20:00 |     1

此示例大致如下所示:

<-------------- entry 1 interval ---------------------------------->
     <-booking 1->   <-booking 2->                     <-booking 3->

现在我想获得条目1尚未预订的所有时间间隔:

期望的结果:

entry_id |        start        |           end
----------------------------------------------------
1        | 2014-07-24 09:00:00 | 2014-07-24 09:10:00
1        | 2014-07-24 09:40:00 | 2014-07-24 09:50:00
1        | 2014-07-24 10:20:00 | 2014-07-24 10:50:00

我如何使用SQL执行此操作? (我正在寻找一个通用的解决方案,虽然我将在MySQL中使用它)

3 个答案:

答案 0 :(得分:1)

MS SQL

SELECT * FROM
(
    SELECT ENTRY_ID, 
    b1.BOOK_END_DATE AS INTERVALL_START_DATE, 
    (SELECT TOP 1 b2.BOOK_START_DATE FROM Booking b2 WHERE b1.BOOK_ENTRY_ID = b2.BOOK_ENTRY_ID AND b2.BOOK_START_DATE > b1.BOOK_END_DATE ORDER BY b2.BOOK_START_DATE) AS INTERVALL_END_DATE
    FROM Entry
    JOIN Booking b1 ON ENTRY_ID = b1.BOOK_ENTRY_ID
) AS sub01
WHERE sub01.INTERVALL_START_DATE is not null AND sub01.INTERVALL_END_DATE is not null AND sub01.INTERVALL_START_DATE <> sub01.INTERVALL_END_DATE
UNION 
(SELECT ENTRY_ID, ENTRY_START_DATE, (SELECT TOP 1 BOOK_START_DATE FROM Booking WHERE BOOK_ENTRY_ID = ENTRY_ID AND BOOK_START_DATE > ENTRY_START_DATE ORDER BY BOOK_START_DATE) FROM Entry)
UNION 
(SELECT ENTRY_ID, (SELECT TOP 1 BOOK_END_DATE FROM Booking WHERE BOOK_ENTRY_ID = ENTRY_ID AND BOOK_END_DATE < ENTRY_END_DATE ORDER BY BOOK_END_DATE DESC), ENTRY_END_DATE FROM Entry)

<强>的MySQL

SELECT * FROM
(
SELECT * FROM
(
    SELECT ENTRY_ID, 
    b1.BOOK_END_DATE AS INTERVALL_START_DATE, 
    (SELECT b2.BOOK_START_DATE FROM Booking b2 WHERE b1.BOOK_ENTRY_ID = b2.BOOK_ENTRY_ID AND b2.BOOK_START_DATE > b1.BOOK_END_DATE ORDER BY b2.BOOK_START_DATE LIMIT 1) AS INTERVALL_END_DATE
    FROM Entry
    JOIN Booking b1 ON ENTRY_ID = b1.BOOK_ENTRY_ID
) AS sub01
WHERE sub01.INTERVALL_START_DATE is not null AND sub01.INTERVALL_END_DATE is not null
UNION
(SELECT e2.ENTRY_ID, e2.ENTRY_START_DATE, (SELECT b2.BOOK_START_DATE FROM Booking b2 WHERE b2.BOOK_ENTRY_ID = e2.ENTRY_ID AND b2.BOOK_START_DATE >= e2.ENTRY_START_DATE ORDER BY b2.BOOK_START_DATE LIMIT 1) FROM Entry e2)
UNION
(SELECT e3.ENTRY_ID, (SELECT b3.BOOK_END_DATE FROM Booking b3 WHERE b3.BOOK_ENTRY_ID = e3.ENTRY_ID AND BOOK_END_DATE <= e3.ENTRY_END_DATE ORDER BY b3.BOOK_END_DATE DESC LIMIT 1), e3.ENTRY_END_DATE FROM Entry e3)
 ) sub02
WHERE sub02.INTERVALL_START_DATE <> sub02.INTERVALL_END_DATE

Fiddle

答案 1 :(得分:1)

这是一个想法。而不是一系列“to”/“from”或“start”/“end”日期必须保持同步而没有重叠,而是使用只有一个日期的设计。

CREATE TABLE Entry (
  ID INT NOT NULL,
  EffDate datetime not null,
  State smallint NOT NULL,
  Comment varchar( 100 ),
  primary key( ID, EffDate, State )
);

这里的关键是每个州的变化都有记录。国家基本上是预订(不可用)而未预订(可用)。因此,当您创建条目时,您会在条目开始时写一行,而在条目结束时写一行。

INSERT INTO Entry( ID, EffDate, State, Comment )
select 1, STR_TO_DATE( '2014-07-24 09:00', '%Y-%m-%d %h:%i' ), -2, 'Entry Open' union all
select 1, STR_TO_DATE( '2014-07-24 11:20', '%Y-%m-%d %h:%i' ), 0, 'Entry Closed';

这些状态为零或负值。它们可以是任何值,我只是选择它们,因为它们允许方便的分组。有一个状态-1,你马上就会看到。

有两个查询可以为您提供您想知道的内容:一个查询可以显示条目何时被预订,一个查询可以显示条目何时可用以及显示多长时间。

-- When is the entry available?
  select e1.ID as Entry, e1.Comment as Availability, null as `Booking #`,
         e1.EffDate as `From`, e2.EffDate as `To`
    from Entry e1
    left join Entry e2
      on e2.ID = e1.ID
     and e2.EffDate =(
           select Min( EffDate )
             from Entry
            where ID = e1.ID
              and State >= 0
              and EffDate > e1.EffDate)
   where e1.State <= 0;

此时,结果集如下所示:

ENTRY AVAILABILITY  BOOKING # FROM                  TO
1     Entry Open    (null)    July, 24 2014 09:00   July, 24 2014 11:20
1     Entry Closed  (null)    July, 24 2014 11:20   (null)

您稍后会看到“预订#”列的内容。查询显示预订:

-- When is the entry booked?
  select e1.ID as Entry, e1.Comment as Availability, e1.State as `Booking #`,
         e1.EffDate as `From`, e2.EffDate as `To`
    from Entry e1
    join Entry e2
      on e2.ID = e1.ID
     and e2.EffDate =(
           select Min( EffDate )
             from Entry
            where ID = e1.ID
              and State in( -1, 0 )
              and EffDate > e1.EffDate)
   where e1.State > 0;

此时,这没有任何结果。还没有预订。所以让我们生成一个预订。如您所见,整个条目都可用,因此我们要做的是一组“预订开始”和“预订结束”状态行以及适当的时间。

INSERT INTO Entry( ID, EffDate, State, Comment )
select 1, STR_TO_DATE( '2014-07-24 09:10', '%Y-%m-%d %h:%i' ), 1, 'Booked' union all
select 1, STR_TO_DATE( '2014-07-24 09:40', '%Y-%m-%d %h:%i' ), -1, 'Booking Available';

预订的州值是预订号码(1,2等),预订结束是最终的负面状态,-1。现在让我们看一下我们的查询显示的内容,但让我们将它们结合在一起以获得完整的外观:

select *
from(
  (first query)
union
  (second query)
) B
order by B.Entry, B.`From`;

ENTRY AVAILABILITY      BOOKING #   FROM                    TO
1     Entry Open        (null)      July, 24 2014 09:00     July, 24 2014 09:10
1     Booked            1           July, 24 2014 09:10     July, 24 2014 09:40
1     Booking Available (null)      July, 24 2014 09:40     July, 24 2014 11:20
1     Entry Closed      (null)      July, 24 2014 11:20     (null)

另有预订。我们可以看到时间段从9:00到9:10以及9:40到11:20。预订者在9:50到10:20做出决定,所以我们通过编写一对状态变更记录进行预订。

INSERT INTO Entry( ID, EffDate, State, Comment )
select 1, STR_TO_DATE( '2014-07-24 09:50', '%Y-%m-%d %h:%i' ), 2, 'Booked' union all
select 1, STR_TO_DATE( '2014-07-24 10:20', '%Y-%m-%d %h:%i' ), -1, 'Booking Available';

如果他们想在其他预订完成时立即开始,10:40,那么我们可以更新现有的“预订可用”到第二次预订结束,10:20,并插入预订2记录。预订开始后,当条目可用或另一个预订开始时结束。无关紧要。

ENTRY AVAILABILITY      BOOKING #   FROM                    TO
1     Entry Open        (null)      July, 24 2014 09:00     July, 24 2014 09:10
1     Booked            1           July, 24 2014 09:10     July, 24 2014 09:40
1     Booking Available (null)      July, 24 2014 09:40     July, 24 2014 09:50
1     Booked            2           July, 24 2014 09:50     July, 24 2014 10:20
1     Booking Available (null)      July, 24 2014 10:20     July, 24 2014 11:20
1     Entry Closed      (null)      July, 24 2014 11:20     (null)

最后,条目从10:50预订到条目结尾。我们写了Booked记录但是因为它到目前为止的结尾,我们不需要以预订可用行结束它。

INSERT INTO Entry( ID, EffDate, State, Comment )
select 1, STR_TO_DATE( '2014-07-24 10:50', '%Y-%m-%d %h:%i' ), 3, 'Booked';

现在我们可以看到完整的图片:

ENTRY AVAILABILITY      BOOKING #   FROM                    TO
1     Entry Open        (null)      July, 24 2014 09:00     July, 24 2014 09:10
1     Booked            1           July, 24 2014 09:10     July, 24 2014 09:40
1     Booking Available (null)      July, 24 2014 09:40     July, 24 2014 09:50
1     Booked            2           July, 24 2014 09:50     July, 24 2014 10:20
1     Booking Available (null)      July, 24 2014 10:20     July, 24 2014 10:50
1     Booked            3           July, 24 2014 10:50     July, 24 2014 11:20
1     Entry Closed      (null)      July, 24 2014 11:20     (null)

如果其他人想要预订,您可以查看此列表,找到预订#为空的位置并阅读以查看可用剩余时间:9:00-9:10,9:40-9:50,10 :20-10:50。或者您读下“从”和“可用性”列以查看操作顺序:条目在9:00“在线”,在9:10预订,在9:40可用,在9:50再次预订,可在10:20,在10:50预订,直到该条目在11:20进入“离线”。

此方法的一个很好的功能是您不必担心重叠间隔。一旦状态开始,它将一直有效,直到下一个状态发生变化。这使得重叠绝对不可能。

SQL Fiddle

中查看整个行动

答案 2 :(得分:0)

有点难看:

SELECT
  e.id                           AS entry_id,
  e.start                        AS start,
  COALESCE(MIN(b1.start), e.end) AS end
FROM entry e
  LEFT JOIN booking b1 ON b1.entry_id = e.id AND b1.start >= e.start AND b1.end <= e.end
GROUP BY e.id
HAVING start < end
UNION
SELECT
  e.id                           AS entry_id,
  b1.end                         AS start,
  COALESCE(MIN(b2.start), e.end) AS end
FROM entry e
  JOIN booking b1 ON b1.entry_id = e.id AND b1.start >= e.start AND b1.end <= e.end
  LEFT JOIN booking b2 ON b2.entry_id = e.id AND b2.start >= b1.end AND b2.end <= e.end
GROUP BY b1.id
HAVING start < end;

针对MySQL进行测试;对于其他SQL-Servers,您可能需要在GROUP BY术语中包含更多列。没有使用特定于MySQL的函数。

它做什么?

最多UNION的声明决定了第一次预订前剩余的时间,每个条目最多返回一行。

UNION之后的陈述选择了该条目的所有预订,并在预订后将所有预订与所有预订配对。

删除0长度间隔需要

HAVING,如果许多预订直接相邻,则只会破坏运行时间。

加成:

我这是一个稍微修改过的查询,适合轻松选择条目,预订不完全包含在条目和订购中:

SELECT
  entry_id,
  start,
  end
FROM (
       SELECT
         e.id                           AS entry_id,
         e.start                        AS start,
         COALESCE(MIN(b1.start), e.end) AS end
       FROM entry e
         LEFT JOIN booking b1 ON b1.entry_id = e.id AND b1.start <= e.end AND b1.end >= e.start
       GROUP BY e.id
       UNION
       SELECT
         e.id                           AS entry_id,
         b1.end                         AS start,
         COALESCE(MIN(b2.start), e.end) AS end
       FROM entry e
         JOIN booking b1 ON b1.entry_id = e.id AND b1.start <= e.end AND b1.end >= e.start
         LEFT JOIN booking b2 ON b2.entry_id = e.id AND b2.start >= b1.end AND b2.start <= e.end AND b2.end >= e.start
       GROUP BY b1.id) iq
WHERE start < end AND entry_id = 1
ORDER BY start;