Как узнать, охватывает ли набор диапазона дат больший диапазон дат? - PullRequest
0 голосов
/ 12 июля 2019

У меня есть лица: postes и bookings. Между poste и booking существует отношение oneToMany: у одного поста может быть много заказов (на разные даты).

bookings определяется 4 столбцами:

  • booking_id: id
  • poste_id: соединение postes стол
  • start_datetime: дата начала бронирования
  • number_day: количество дней (целое число)

postes определяется 4 столбцами:

  • poste_id: почтовый индекс
  • pattern (строка): определен разрешенный день (1 разрешено, 0 нет). 8-й день определяется как 1-й день модели (по модулю 7)
  • start: дата начала поста (все даты в бронировании указаны между началом и концом)
  • end: конечная дата окончания

Цель : Я хотел бы определить запрос, который выбирает все postes, которые не полностью зарезервированы (например, возможны некоторые новые резервирования). Я застрял, потому что я не могу выбрать какие-либо данные для свободного диапазона дат, так как хранятся только бронирования.

Пример * ** 1053 тысяча пятьдесят две * Booking table | booking_id | poste_id | start_datetime | number_day | |------------|----------|----------------------|------------| | 1 | 1 | 2019-07-10T00:00:00Z | 4 | | 4 | 1 | 2019-07-14T00:00:00Z | 1 | | 7 | 1 | 2019-07-16T00:00:00Z | 4 | | 2 | 2 | 2019-07-10T00:00:00Z | 2 | | 9 | 2 | 2019-07-13T00:00:00Z | 2 | | 5 | 3 | 2019-07-15T00:00:00Z | 2 | | 8 | 3 | 2019-07-21T00:00:00Z | 3 | | 11 | 3 | 2019-07-28T00:00:00Z | 1 | | 12 | 3 | 2019-07-29T00:00:00Z | 1 | | 3 | 4 | 2019-07-15T00:00:00Z | 1 | | 13 | 4 | 2019-07-21T00:00:00Z | 2 | Postes table: | poste_id | pattern | start | end | |----------|---------|----------------------|----------------------| | 1 | 1111101 | 2019-07-10T00:00:00Z | 2019-07-20T00:00:00Z | | 2 | 1101101 | 2019-07-10T00:00:00Z | 2019-07-20T00:00:00Z | | 3 | 1100001 | 2019-07-15T00:00:00Z | 2019-07-30T00:00:00Z | | 4 | 1011001 | 2019-07-15T00:00:00Z | 2019-07-30T00:00:00Z | Ожидаемый результат примера: 2,4. (бронирование не доступно для почты 1 и 3).

Примечание :

  • Это простой пример. На самом деле, диапазоны дат больше, например в течение нескольких месяцев.
  • В базе данных предполагается, что бронирования не перекрываются .
  • Длина шаблона может отличаться от 7. Нет связи между первым днем ​​шаблона и количеством дней в неделе. Например, если шаблон «1101» с начальной датой «10-07-2019», это означает, что доступны дни 10, 11, 13, 14, 15, 17 и т. Д., А не остальные.
  • Даты бронирования всегда находятся между начальной и конечной датой poste.

воспроизводимость:

// Build the tables:
CREATE TABLE bookings
    (`booking_id` int, `poste_id` int, `start_datetime` datetime, `number_day` int)
;

INSERT INTO bookings
    (`booking_id`, `poste_id`, `start_datetime`, `number_day`)
VALUES
    (1, 1, '2019-07-10', '4'),
    (4, 1, '2019-07-14', '1'),
    (7, 1, '2019-07-16', '4'),
    (2, 2, '2019-07-10', '2'),
    (9, 2, '2019-07-13', '2'),
    (5, 3, '2019-07-15', '2'),
    (8, 3, '2019-07-21', '3'),
    (11, 3, '2019-07-28', '1'),
    (12, 3, '2019-07-29', '1'),
    (3, 4, '2019-07-15', '1'),
    (13, 4, '2019-07-21', '2')
;

CREATE TABLE postes
    (`poste_id` int, `pattern` VARCHAR(7), `start` datetime, `end` datetime);

INSERT INTO postes VALUES 
  (1, "1111101", "2019-07-10", "2019-07-20"),
  (2, "1101101", "2019-07-10", "2019-07-20"),
  (3, "1100001", "2019-07-15", "2019-07-30"),
  (4, "1011001", "2019-07-15", "2019-07-30");

Моя работа : до сих пор мне удалось найти за данный день доступный пост:

   SELECT DISTINCT p.* 
     FROM postes p
LEFT JOIN bookings b
       ON b.poste_id = p.poste_id
    WHERE
          /* Ignore date in past */
          MOD(DATEDIFF("2019-07-16", p.start), LENGTH(p.pattern)) >= -1

      AND
          /* Filter poste with pattern = 1 */
          SUBSTRING(p.pattern, MOD(DATEDIFF("2019-07-16", p.start),
                                   LENGTH(p.pattern)) + 1 , 1) = 1
      AND 
          /* Filter those available this day */
          p.poste_id NOT IN (
                SELECT b.poste_id
                  FROM bookings b
                 WHERE b.start_datetime <= "2019-07-16"
                   AND "2019-07-16" < DATE_ADD(b.start_datetime, INTERVAL b.number_day DAY)
                             );

Выход:

| poste_id | pattern |                start |                  end |
|----------|---------|----------------------|----------------------|
|        2 | 1101101 | 2019-07-10T00:00:00Z | 2019-07-20T00:00:00Z |

Ответы [ 4 ]

1 голос
/ 14 июля 2019

Начиная с 8.0 вы можете сделать это с помощью сгенерированной на лету таблицы чисел и небольшой помощи LATERAL.Вместо этого вы можете создать постоянную таблицу чисел.

with e1(n) as (
        select 1 union all select 1 union all select 1 union all
        select 1 union all select 1 union all select 1 union all
        select 1 union all select 1 union all select 1 union all select 1
), e2(n) as (select 1 from e1 a, e1 b), -- 100 rows
   e4(n) as (select 1 from e2 a, e2 b), -- 10,000 rows
numbers(n) as (
   select row_number() over(order by n) N from e4
)
select distinct poste_id, pattern, start, `end` 
from postes p 
join numbers n on adddate(start, n.N-1) <= `end`
   --  compute the date and respective position in the pattern for further usage
   , lateral (select adddate(start, n.N-1) dt, (n.N-1) % length(pattern) + 1 pos) x
where substring(pattern, x.pos, 1)
and not exists (
     select 1 
     from bookings b
     where b.poste_id = p.poste_id and x.dt >= b.start_datetime and x.dt <= adddate(b.start_datetime, b.number_day))
order by p.poste_id;

Fiddle

1 голос
/ 14 июля 2019

(Еще не полный ответ, но хотя бы несколько советов ...)

  • 2-й столбец: bookings poste_id или room_id?(Полагаю, слово "комната" было бы лучшим словом для английского ??)
  • Используйте DATE тип данных вместо DATETIME.(Кроме того, MySQL будет подавлять T и Z в литералах даты и времени.)
  • Вместо VARCHAR(7) используйте TINYINT UNSIGNED.Это позволит вам использовать логические операции, операции сдвига и функцию BIT_COUNT().(См. https://dev.mysql.com/doc/refman/8.0/en/bit-functions.html.) Это должно облегчить необходимые вычисления.
  • Ваша битовая строка имеет длину 7, как будто она связана с днями недели.Но так ли это?То есть, первое ли это связано, скажем, с воскресеньем?Или это связано с poste.start?
  • Какую версию MySQL вы используете?До 8.0 битовые операции ограничивались 64, следовательно, решение для битовой манипуляции ограничивалось 2 месяцами.С 8.0 размер операций практически неограничен.

Итак, с 8.0 я мог бы

  1. Реплицировать комбинацию битов (не символов) достаточное количество раз.(Хммм ... REPEAT работает легко для символов, но не для битов. Возможно, сделайте повторение со строками символов, затем преобразуйте в биты.)
  2. Отрежьте биты после даты end.
  3. BIT_COUNT() чтобы узнать, сколько дней доступно в диапазоне.
  4. SUM(number_day), чтобы узнать количество зарезервированных дней.
  5. Вычтите, чтобы узнать, сколько дней не зарезервировано.(Примечание: это предполагает, что данные являются «действительными», то есть не имеют каких-либо «перекрытий» в bookings.

(я, вероятно, написал бы код на «реальном» языке программирования,как предложено Aprillion. Мои шаги выше могут быть полезными там.)

С более старой версией MySQL и / или с VARCHAR(7) вместо TINYINT вышеупомянутые шаги могутработать, но с некоторыми заменами. Например, BIT_COUNT можно заменить на LENGTH(s) - LENGTH(REPLACE(s, '1', ''))

1 голос
/ 14 июля 2019

Это довольно распространенная проблема в вопросах SQL - как сопоставить данные, которых нет в базе данных? SQL лучше сопоставляет данные с .

Другое качество SQL заключается в том, что он лучше работает с наборами строк, а не с воображаемыми диапазонами, определяемыми начала и конца. Поэтому моя стратегия - преобразовать диапазон в наборы фактических строк.

Сначала создайте таблицу всех дат:

CREATE TABLE dates (date DATE PRIMARY KEY);
INSERT INTO dates SET date = '2019-07-01';
... 
INSERT INTO dates SET date = '2019-07-30';

Создайте таблицу всех дат, используемых любым постом:

CREATE TABLE poste_dates (
  poste_id INT, 
  date DATE, 
  booking_id INT,
  PRIMARY KEY (poste_id, date)
);

Заполните его всеми датами для каждой даты, между началом и концом диапазона дат. Отфильтруйте, используя условие соединения, так что дни недели, соответствующие вашему шаблону, равны '1'.

INSERT INTO poste_dates (poste_id, date) 
SELECT poste_id, d.date FROM postes p JOIN dates d 
  ON SUBSTR(p.pattern, MOD(DATEDIFF(d.date, p.start), LENGTH(p.pattern))+1, 1) 
WHERE d.date BETWEEN p.start AND p.end;

Query OK, 34 rows affected (0.01 sec)

Теперь у вас есть все даты для всех постов:

+----------+------------+------------+
| poste_id | date       | booking_id |
+----------+------------+------------+
|        1 | 2019-07-10 |       NULL |
|        1 | 2019-07-11 |       NULL |
|        1 | 2019-07-12 |       NULL |
|        1 | 2019-07-13 |       NULL |
|        1 | 2019-07-14 |       NULL |
|        1 | 2019-07-17 |       NULL |
|        1 | 2019-07-18 |       NULL |
|        1 | 2019-07-19 |       NULL |
|        1 | 2019-07-20 |       NULL |
|        2 | 2019-07-10 |       NULL |
|        2 | 2019-07-11 |       NULL |
|        2 | 2019-07-13 |       NULL |
|        2 | 2019-07-14 |       NULL |
|        2 | 2019-07-17 |       NULL |
|        2 | 2019-07-18 |       NULL |
|        2 | 2019-07-20 |       NULL |
|        3 | 2019-07-15 |       NULL |
|        3 | 2019-07-16 |       NULL |
|        3 | 2019-07-22 |       NULL |
|        3 | 2019-07-23 |       NULL |
|        3 | 2019-07-29 |       NULL |
|        3 | 2019-07-30 |       NULL |
|        4 | 2019-07-15 |       NULL |
|        4 | 2019-07-17 |       NULL |
|        4 | 2019-07-18 |       NULL |
|        4 | 2019-07-22 |       NULL |
|        4 | 2019-07-24 |       NULL |
|        4 | 2019-07-25 |       NULL |
|        4 | 2019-07-29 |       NULL |
+----------+------------+------------+

Для каждого бронирования используйте UPDATE, чтобы установить идентификатор бронирования в таблице poste_dates. Используйте LIMIT с длиной бронирования. Мы должны делать это по одному, потому что в MySQL LIMIT не работает, когда UPDATE имеет JOIN.

UPDATE poste_dates SET booking_id =  1 WHERE poste_id = 1 AND date >= '2019-07-10' ORDER BY date LIMIT 4;
UPDATE poste_dates SET booking_id =  4 WHERE poste_id = 1 AND date >= '2019-07-14' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id =  7 WHERE poste_id = 1 AND date >= '2019-07-16' ORDER BY date LIMIT 4;
UPDATE poste_dates SET booking_id =  2 WHERE poste_id = 2 AND date >= '2019-07-10' ORDER BY date LIMIT 2;
UPDATE poste_dates SET booking_id =  9 WHERE poste_id = 2 AND date >= '2019-07-13' ORDER BY date LIMIT 2;
UPDATE poste_dates SET booking_id =  5 WHERE poste_id = 3 AND date >= '2019-07-15' ORDER BY date LIMIT 2;
UPDATE poste_dates SET booking_id =  8 WHERE poste_id = 3 AND date >= '2019-07-21' ORDER BY date LIMIT 3;
UPDATE poste_dates SET booking_id = 11 WHERE poste_id = 3 AND date >= '2019-07-28' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 12 WHERE poste_id = 3 AND date >= '2019-07-29' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id =  3 WHERE poste_id = 4 AND date >= '2019-07-15' ORDER BY date LIMIT 1;
UPDATE poste_dates SET booking_id = 13 WHERE poste_id = 4 AND date >= '2019-07-21' ORDER BY date LIMIT 2;

Теперь даты выглядят так:

+----------+------------+------------+
| poste_id | date       | booking_id |
+----------+------------+------------+
|        1 | 2019-07-10 |          1 |
|        1 | 2019-07-11 |          1 |
|        1 | 2019-07-12 |          1 |
|        1 | 2019-07-13 |          1 |
|        1 | 2019-07-14 |          4 |
|        1 | 2019-07-16 |          7 |
|        1 | 2019-07-17 |          7 |
|        1 | 2019-07-18 |          7 |
|        1 | 2019-07-19 |          7 |
|        1 | 2019-07-20 |       NULL |
|        2 | 2019-07-10 |          2 |
|        2 | 2019-07-11 |          2 |
|        2 | 2019-07-13 |          9 |
|        2 | 2019-07-14 |          9 |
|        2 | 2019-07-16 |       NULL |
|        2 | 2019-07-17 |       NULL |
|        2 | 2019-07-18 |       NULL |
|        2 | 2019-07-20 |       NULL |
|        3 | 2019-07-15 |          5 |
|        3 | 2019-07-16 |          5 |
|        3 | 2019-07-21 |          8 |
|        3 | 2019-07-22 |          8 |
|        3 | 2019-07-23 |          8 |
|        3 | 2019-07-28 |         11 |
|        3 | 2019-07-29 |         12 |
|        3 | 2019-07-30 |       NULL |
|        4 | 2019-07-15 |          3 |
|        4 | 2019-07-17 |       NULL |
|        4 | 2019-07-18 |       NULL |
|        4 | 2019-07-21 |         13 |
|        4 | 2019-07-22 |         13 |
|        4 | 2019-07-24 |       NULL |
|        4 | 2019-07-25 |       NULL |
|        4 | 2019-07-28 |       NULL |
|        4 | 2019-07-29 |       NULL |
+----------+------------+------------+

Теперь довольно просто искать любые посты, у которых есть какие-либо даты в этой таблице, с NULL booking_id.

SELECT DISTINCT poste_id FROM poste_dates WHERE booking_id IS NULL;

Это все еще отличается от вашего ожидаемого результата постов 2 и 4.

  • Пост 1 включает дату 2019-07-20, потому что паттерн 1111101-1111, который ставит 1 на 20, но никакое бронирование для Пост 1 не охватывает 20. Поэтому 1 забронировано не полностью.
  • Пост 3 включает в себя дату 2019-07-30, потому что шаблон 1100001-1100001-11, который ставит 1 на 30, но никакое бронирование на Пост 3 не покрывает 30. Поэтому 3 забронировано не полностью.
1 голос
/ 14 июля 2019

Из-за poste.pattern я не понимаю, как можно было бы работать с диапазонами дат напрямую.Можно расширить решение на 1 день, присоединившись к таблице, в которой перечислены все даты из диапазона в отдельных строках, которые можно сгенерировать следующим образом:

Как заполнить таблицус диапазоном дат?

... заменив "2019-07-16" на столбец _date из этой таблицы.

Примечание: для этой задачи использование языка программирования может быть более производительным, чем подход, основанный только на SQL.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...