【问题标题】:Get 1 hour incrementation for room availability MySQL获得 1 小时的房间可用性增量 MySQL
【发布时间】:2018-12-31 15:39:49
【问题描述】:

我正在尝试以 1 小时为增量来获取我酒店的房间可用性。因此,例如,如果房间是从上午 9 点到上午 10 点以及从上午 12 点到下午 3 点预订的,我试图获取 available_from 到 available_to 之间所有其他时间的 1 小时增量

我可以在桌子上留下加入并获得房间可用性,但不能获得时间段。

这是我的相关架构:

酒店:

Id | name

预订:

Id | hotel_id | room_id | start | end | status

房间:

Id | hotel_id | name | number | available_from | available_to

这是我目前的查询:

SELECT r.id, r.name, r.number, r.type, r.rating
FROM rooms r
    LEFT OUTER JOIN reservations res ON res.room_id = r.id 
        AND CURRENT_TIMESTAMP BETWEEN r.available_from AND r.available_to
GROUP BY r.id, r.type

示例:

(这是我试图从数据库中取回的数组。忽略属性名称):

[{"roomNumber":1,"availableTimes":["2019-01-01 00:00:00","2019-01-01 01:00:00","2019-01-01 02: 00:00","2019-01-01 03:00:00","2019-01-01 04:00:00","2019-01-01 05:00:00","2019-01-01 06:00:00","2019-01-01 07:00:00","2019-01-01 08:00:00","2019-01-01 09:00:00","2019-01 -01 10:00:00","2019-01-01 11:00:00","2019-01-01 12:00:00","2019-01-01 13:00:00","2019 -01-01 14:00:00","2019-01-01 15:00:00","2019-01-01 16:00:00","2019-01-01 17:00:00", "2019-01-01 18:00:00","2019-01-01 19:00:00","2019-01-01 20:00:00","2019-01-01 21:00:00 ","2019-01-01 22:00:00","2019-01-01 23:00:00"]}]

我尝试了以下方法:

SELECT free_from, free_until
FROM (
  SELECT a.end AS free_from,
  (SELECT MIN(c.start)
   FROM reservations c
   WHERE c.start > a.end) as free_until
  FROM reservations a
  WHERE NOT EXISTS (
    SELECT 1
    FROM reservations b
    WHERE b.start BETWEEN a.end AND a.end + INTERVAL 1 HOUR
  )
  AND a.end BETWEEN '2019-01-03 09:00' AND '2019-01-03 21:00'
) as d
ORDER BY free_until-free_from
LIMIT 0,3;

但是我得到的一行只返回了 1 个结果,这也是不正确的。我该如何解决这个问题?

样本数据:

酒店:

1 | Marriott

预订:

1 | 1 | 1 | 2019-01-03 15:00:00 | 2019-01-03 17:00:00 | Confirmed
1 | 1 | 1 | 2019-01-03 18:00:00 | 2019-01-03 20:00:00 | Confirmed

房间:

1 | 1 | "Single" | 528 | 09:00:00 | 21:00:00

预期结果

房间号 |房间名称 |可用时间

1 | "Single" | 2019-01-03 09:00:00, 2019-01-03 10:00:00, 2019-01-03 11:00:00, 2019-01-03 12:00:00, 2019-01-03 13:00:00, 2019-01-03 14:00:00, 2019-01-03 17:00:00, 2019-01-03 20:00:00, 2019-01-03 21:00:00, 2019-01-03 22:00:00, 2019-01-03 23:00:00, 2019-01-03 24:00:00

【问题讨论】:

  • 我看不清楚,如果您提供一些示例数据和预期结果,我将不胜感激。
  • @ErayBalkanli 添加了
  • 所以在你的例子中,房间:1 在 2019 年 1 月 1 日全天可用,对吗? @HeelMega
  • 这是正确的@ErayBalkanli
  • Rooms 表中有available_from 和available_to,Reservations 表中有Start 和End 字段,它们有什么区别,为什么需要Reservations 表?在您的示例查询中,您也没有使用 Reservations 表中的任何字段吗? @HeelMega

标签: mysql sql join


【解决方案1】:

如果您将 Times_Slots 表添加到数据库中,如 SQL Fiddle 所示:

CREATE TABLE Time_Slots
    (`Slot` time);

INSERT INTO Time_Slots
    (`Slot`)
VALUES
    ('00:00:00'),
    ('01:00:00'),
    ('02:00:00'),
    ('03:00:00'),
    ('04:00:00'),
    ('05:00:00'),
    ('06:00:00'),
    ('07:00:00'),
    ('08:00:00'),
    ('09:00:00'),
    ('10:00:00'),
    ('11:00:00'),
    ('12:00:00'),
    ('13:00:00'),
    ('14:00:00'),
    ('15:00:00'),
    ('16:00:00'),
    ('17:00:00'),
    ('18:00:00'),
    ('19:00:00'),
    ('20:00:00'),
    ('21:00:00'),
    ('22:00:00'),
    ('23:00:00');

那么以下查询将提供所有已预订房间的空房情况:

查询 1

select r.id
     , r.Name
     , res_date + interval t.slot hour_second available
  from Time_Slots t
  join Rooms r
    on t.Slot between r.available_from and r.available_to
  join (select distinct room_id, date(start) res_date from Reservation) res
    on res.room_id = r.id
 where (r.id, res_date + interval t.slot hour_second) not in (
        select r.room_id
             , date(r.start) + interval t.slot hour_second Reserved
          from Time_Slots t
          join Reservation r
            on r.start <= date(r.end) + interval t.slot hour_second
           and date(r.start) + interval t.slot hour_second < r.end)

此查询的工作原理是首先从Times_Slots 中为该房间至少有一个预订的每一天选择可用时隙,然后过滤掉已预订的时隙。

Results

| id |   Name |            available |
|----|--------|----------------------|
|  1 | Single | 2019-01-03T09:00:00Z |
|  1 | Single | 2019-01-03T10:00:00Z |
|  1 | Single | 2019-01-03T11:00:00Z |
|  1 | Single | 2019-01-03T12:00:00Z |
|  1 | Single | 2019-01-03T13:00:00Z |
|  1 | Single | 2019-01-03T14:00:00Z |
|  1 | Single | 2019-01-03T17:00:00Z |
|  1 | Single | 2019-01-03T20:00:00Z |
|  1 | Single | 2019-01-03T21:00:00Z |

在您的示例输出中,您指出房间在 2019-01-03 22:00:00、2019-01-03 23:00:00、2019-01-03 24:00:00 可用,但是那些时间是在房间表定义的可用性块之后,所以我的查询排除了这些时间。

【讨论】:

  • hour_second 是多少?
  • 我正在使用区间表达式来处理日期/时间哮喘,因为您的预订将开始和结束存储为日期时间或时间戳值,而您的房间表和我的 time_slots 表只存储时间。当我尝试将时间数据类型添加到 datetime/timestamp 数据类型时,MySQL 不喜欢它,但如果我将时间数据转换为区间表达式就可以了。
【解决方案2】:

您遇到的第一个问题是您的架构设置很差。您没有良好的数据规范化。 1) 重命名字段以获得更好的清晰度。 2) 把这两张表改成这样:

Reservation:
Res_ID | hotel_id | room_id | res_start | res_end | status

Rooms:
Room_ID | hotel_id | room_name | room_number | available_from | available_to

您将需要一个定义了您的时隙的表格。您可以使用 CTE 进行此操作,然后使用您的房间 CROSS JOIN 进行操作。这是CROSS JOIN 有用的少数情况之一。

现在这样查询:

WITH timeslots AS (
    SELECT CURRENT_DATE() AS time_slot UNION
    SELECT CURRENT_DATE() + 1/24   UNION
    SELECT CURRENT_DATE() + 2/24   UNION
    SELECT CURRENT_DATE() + 3/24   UNION
    SELECT CURRENT_DATE() + 4/24   UNION
    SELECT CURRENT_DATE() + 5/24   UNION
    SELECT CURRENT_DATE() + 6/24   UNION
    SELECT CURRENT_DATE() + 7/24   UNION
    SELECT CURRENT_DATE() + 8/24   UNION
    SELECT CURRENT_DATE() + 9/24   UNION
    SELECT CURRENT_DATE() + 10/24  UNION
    SELECT CURRENT_DATE() + 11/24  UNION
    SELECT CURRENT_DATE() + 12/24  UNION
    SELECT CURRENT_DATE() + 13/24  UNION
    SELECT CURRENT_DATE() + 14/24  UNION
    SELECT CURRENT_DATE() + 15/24  UNION
    SELECT CURRENT_DATE() + 16/24  UNION
    SELECT CURRENT_DATE() + 17/24  UNION
    SELECT CURRENT_DATE() + 18/24  UNION
    SELECT CURRENT_DATE() + 19/24  UNION
    SELECT CURRENT_DATE() + 20/24  UNION
    SELECT CURRENT_DATE() + 21/24  UNION
    SELECT CURRENT_DATE() + 22/24  UNION
    SELECT CURRENT_DATE() + 23/24 )
SELECT r.id, r.name, r.number, r.type, r.rating, 
       t.time_slot AS time_slot_open, 
       t.time_slot + 1/24 AS time_slot_close,
       res.Res_ID
FROM rooms r 
     CROSS JOIN timeslots t
     LEFT JOIN reservation res ON res.hotel_id = r.hotel_id AND res.room_id = r.room_id 
         AND time_slot_open >= res.res_start AND time_slot_open < res.res_close

这将为您提供所有酒店房间的列表,每个房间包含 24 条记录。如果该房间有预订,它将显示该位置的预订 ID。从这里,您可以按原样使用数据,也可以进一步将其放入自己的 CTE 中,然后从中选择保留 ID 为空的所有内容。您还可以根据该 ID 加入或查找有关预订的其他数据。

更新

如果您运行 8.0 之前的 MySQL 版本,则不支持 WITH 子句(请参阅:How do you use the "WITH" clause in MySQL?)。您必须将其设为这样的子查询:

SELECT r.id, r.name, r.number, r.type, r.rating, 
       t.time_slot AS time_slot_open, 
       t.time_slot + 1/24 AS time_slot_close,
       res.Res_ID
FROM rooms r 
     CROSS JOIN (SELECT CURRENT_DATE() AS time_slot UNION
     SELECT CURRENT_DATE() + 1/24   UNION
     SELECT CURRENT_DATE() + 2/24   UNION
     SELECT CURRENT_DATE() + 3/24   UNION
     SELECT CURRENT_DATE() + 4/24   UNION
     SELECT CURRENT_DATE() + 5/24   UNION
     SELECT CURRENT_DATE() + 6/24   UNION
     SELECT CURRENT_DATE() + 7/24   UNION
     SELECT CURRENT_DATE() + 8/24   UNION
     SELECT CURRENT_DATE() + 9/24   UNION
     SELECT CURRENT_DATE() + 10/24  UNION
     SELECT CURRENT_DATE() + 11/24  UNION
     SELECT CURRENT_DATE() + 12/24  UNION
     SELECT CURRENT_DATE() + 13/24  UNION
     SELECT CURRENT_DATE() + 14/24  UNION
     SELECT CURRENT_DATE() + 15/24  UNION
     SELECT CURRENT_DATE() + 16/24  UNION
     SELECT CURRENT_DATE() + 17/24  UNION
     SELECT CURRENT_DATE() + 18/24  UNION
     SELECT CURRENT_DATE() + 19/24  UNION
     SELECT CURRENT_DATE() + 20/24  UNION
     SELECT CURRENT_DATE() + 21/24  UNION
     SELECT CURRENT_DATE() + 22/24  UNION
     SELECT CURRENT_DATE() + 23/24 ) t
     LEFT JOIN reservation res ON res.hotel_id = r.hotel_id AND res.room_id = r.room_id 
         AND time_slot_open >= res.res_start AND time_slot_open < res.res_close

【讨论】:

  • MySQL 支持吗?我在那里遇到错误
  • 应该是,至少在 8.0 及更高版本中。我知道早期版本没有它。请参阅此处的文档:dev.mysql.com/doc/refman/8.0/en/with.html
  • 早期版本有替代方案吗?我用的是 mysql 5.6 和 5.x 可能是最常见的版本
  • 我有一个错字,我在 CTE 开头留下了 SELECT。如果这不起作用,那么它可能不受支持。只需将 CROSS JOIN timeslots t 替换为 CROSS JOIN (SELECT CURRENT_DATE …) AS t 即可将其变为子查询
  • 我编辑了答案,向您展示如何解决这个问题。
【解决方案3】:

您需要将房间表与时隙表(24 行)连接起来。这将生成给定房间的所有可能时隙的列表。过滤掉不可用的时间段很简单:

SELECT rooms.id, rooms.name, TIMESTAMP(checkdates.checkdate, timeslots.timeslot) AS datetimeslot
FROM rooms
INNER JOIN (
    SELECT CAST('00:00' AS TIME) AS timeslot UNION
    SELECT CAST('01:00' AS TIME) UNION
    SELECT CAST('02:00' AS TIME) UNION
    SELECT CAST('03:00' AS TIME) UNION
    SELECT CAST('04:00' AS TIME) UNION
    SELECT CAST('05:00' AS TIME) UNION
    SELECT CAST('06:00' AS TIME) UNION
    SELECT CAST('07:00' AS TIME) UNION
    SELECT CAST('08:00' AS TIME) UNION
    SELECT CAST('09:00' AS TIME) UNION
    SELECT CAST('10:00' AS TIME) UNION
    SELECT CAST('11:00' AS TIME) UNION
    SELECT CAST('12:00' AS TIME) UNION
    SELECT CAST('13:00' AS TIME) UNION
    SELECT CAST('14:00' AS TIME) UNION
    SELECT CAST('15:00' AS TIME) UNION
    SELECT CAST('16:00' AS TIME) UNION
    SELECT CAST('17:00' AS TIME) UNION
    SELECT CAST('18:00' AS TIME) UNION
    SELECT CAST('19:00' AS TIME) UNION
    SELECT CAST('20:00' AS TIME) UNION
    SELECT CAST('21:00' AS TIME) UNION
    SELECT CAST('22:00' AS TIME) UNION
    SELECT CAST('23:00' AS TIME)
) AS timeslots ON timeslots.timeslot >= rooms.available_from
              AND timeslots.timeslot <  rooms.available_to
CROSS JOIN (
    SELECT CAST('2019-01-03' AS DATE) AS checkdate
) AS checkdates
WHERE NOT EXISTS (
    SELECT 1
    FROM reservations
    WHERE room_id = rooms.id
    AND TIMESTAMP(checkdates.checkdate, timeslots.timeslot) >= `start`
    AND TIMESTAMP(checkdates.checkdate, timeslots.timeslot) <  `end`
)

Demo on DB Fiddle

上述查询检查某一日期 (2019-01-03) 的可用性。对于多个日期,只需将它们添加到 checkdates

【讨论】:

  • 在您的示例中... 17:00-18:00 未预订,但未显示在结果中
  • @HeelMega 如果预订 15:00-17:00 意味着房间在 17:00 及之后可用,则将 &lt;= 替换为 &lt;。同样,如果从/到 09:00-21:00 可用,则表示房间在 21:00 及之后变得不可用,则将 &lt;= 替换为 &lt;。我已经更新了答案。
【解决方案4】:

A 计划(每小时一排)

  • 去掉 T 和 Z; MySQL 不理解该语法。
  • 您的汽车旅馆位于一个时区,对吗?然后使用DATETIMETIMESTAMP 是等效的。
  • 对于 3 小时的预订,请进行 3 行。 (使用范围可能会更麻烦。)
  • 唉,您使用的是 MySQL,而不是 MariaDB;后者具有自动序列生成器。示例:名为 seq_0_to_23 的伪表的作用类似于预先填充了数字 0 到 23 的表。
  • 查找可用时间需要有一个表,其中包含所有天的所有可能时间,因此需要上面的注释。
  • 算术或 LEFT 数小时:
  • 由于 LEFT 简单明了,

我会讨论它:

mysql> SELECT NOW(), LEFT(NOW(), 13);
+---------------------+-----------------+
| NOW()               | LEFT(NOW(), 13) |
+---------------------+-----------------+
| 2019-01-03 13:43:56 | 2019-01-03 13   |
+---------------------+-----------------+
1 row in set (0.00 sec)

第二列显示了一个字符串,可用于指示当天下午 1 点。

B 计划(范围)

另一种方法使用范围。然而,处理是复杂的,因为所有时间总是与预订或“可用”相关联。代码复杂,但性能不错:http://mysql.rjweb.org/doc.php/ipranges

C 计划(位)

该表包含一个 日期(没有时间),加上一个恰好是 24 位的 MEDIUMINT UNSIGNED。每个位代表一天中的一个小时。

使用各种布尔运算:

  • |(或)这些位一起查看分配的时间。
  • 0xFFFFFF &amp; ~hours 看看有什么可用的。
  • BIT_COUNT() 计算位数(小时)。
  • 虽然可以在 SQL 中确定房间可用的时间,但最好在客户端代码中执行此操作。我假设你有一个 PHP/Java/任何前端!

更多?

您想更详细地讨论这些吗?

【讨论】:

  • T 和 Z 不存在。它已经是日期时间格式。我不小心在帖子中发布了 javascript。没有时隙表就无法获取可用时间吗?
  • 我最喜欢第一种方法。第二和第三会变得太复杂了
  • @HeelMega - 如果你遵循 A 计划,我认为你会被“时隙表”困住。请注意,你在无限远的未来“需要”它。因此,需要一些复杂性来处理在需要时构建它——就像其他答案所做的那样。
猜你喜欢
  • 1970-01-01
  • 2023-04-06
  • 2014-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多