Получите 1 час приращения за доступность комнаты MySQL - PullRequest
0 голосов
/ 31 декабря 2018

Я пытаюсь получить доступность номеров в моем отеле с шагом в 1 час.Так, например, если комната забронирована с 9:00 до 10:00 и с 12:00 до 15:00, я пытаюсь получить приращения в 1 час для всех других периодов времени между available_from и available_to

.оставьте присоединиться к столу и получите доступность комнаты, но только не временные интервалы.

Вот моя связанная схема:

Отель:

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

Ответы [ 4 ]

0 голосов
/ 07 января 2019

Вам необходимо объединить таблицу комнат с таблицей временных интервалов (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`
)

Демонстрация на БД Fiddle

Приведенный выше запрос проверяет наличие на одну дату (2019-01-03),Для нескольких дат просто добавьте их в checkdates.

0 голосов
/ 31 декабря 2018

Если вы добавите таблицу 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 для каждого дня, который имеет хотя бы одно резервирование для этой комнаты, изатем отфильтровываем зарезервированные временные интервалы.

Результаты :

| 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, однако это время после блока доступности таблиц Room, поэтому мой запрос исключил эти времена.

0 голосов
/ 04 января 2019

План A (один ряд в час)

  • Избавиться от T и Z;MySQL не понимает этот синтаксис.
  • Ваш мотель находится в одном часовом поясе, верно?Затем использование DATETIME или TIMESTAMP эквивалентно.
  • Для 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)

ВторойВ столбце показана строка, которая может использоваться для указания часа дня в этот день.

План B (диапазоны)

В другом подходе используются диапазоны.Однако обработка сложна, поскольку все часы всегда связаны либо с резервированием, либо с «доступным».Код становится сложным, но производительность хорошая: http://mysql.rjweb.org/doc.php/ipranges

План C (биты)

Таблица включает дату (нетвремя), плюс MEDIUMINT UNSIGNED, что составляет ровно 24 бита.Каждый бит представляет один час дня.

Используйте различные логические операции:

  • | (ИЛИ) биты вместе, чтобы увидеть, какие часы назначены.
  • 0xFFFFFF & ~hours, чтобы увидеть, что доступно.
  • BIT_COUNT(), чтобы подсчитать биты (часы).
  • Хотя в SQL можно определить, сколько часов доступно комнате, оно можетлучше сделать это в своем клиентском коде.Я предполагаю, что у вас есть PHP / Java / любой другой интерфейс!
  • и т. Д.

Еще?

Хотите обсудить любой изэто более подробно?

0 голосов
/ 31 декабря 2018

Первая проблема, с которой вы столкнулись - плохая настройка вашей схемы.У вас нет хорошей нормализации данных.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записывает каждый.Если в этом номере есть резервирование, то он покажет вам идентификатор бронирования для этого слота.Отсюда вы можете либо использовать данные как есть, либо дополнительно поместить их в свой собственный CTE и просто выбрать из них все, где идентификатор резервирования равен нулю.Вы также можете присоединиться или посмотреть другие данные о резервировании на основе этого идентификатора.

Обновление

Если вы используете версию MySQL до 8.0, WITHпредложение не поддерживается (см .: Как использовать предложение «WITH» в 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...