Поиск по диапазону дат

Quassnoi и другие опубликовали некоторые связанные решения, но они не относятся к этому конкретному случаю.

В таблице MySql примерно с 2 миллионами записей (Id int, StartDate date, EndDate date) и некоторой информацией. Мне нужно изолировать записи, которые касаются определенного диапазона дат (From-To). Пример: какие записи касаются месяца ноября.

SELECT id 
    FROM Records
    WHERE StartDate<="2015-11-30"
      AND EndDate>="2015-11-01"

Разница между датами начала и окончания обычно небольшая, но может быть большой (более одного года). Вот почему предыдущий ответ Квассного не работает. Я не могу заставить его работать в приличное время.

Любая помощь / предложение будет оценено.


person Rafael    schedule 12.05.2015    source источник
comment
Пересекаются ли диапазоны дат? (Не просто касайтесь конечных точек.)   -  person Rick James    schedule 13.05.2015
comment
да. Единственное ограничение: для каждой записи EndDate ›= StartDate. Примером может служить бронирование в отеле. И запрос означает: какие бронирования используют номера в ноябре?   -  person Rafael    schedule 13.05.2015
comment
Ваш запрос должен выполняться достаточно быстро с индексом на startdate, enddate, id. Однако похоже, что он не может ответить на ваш вопрос о бронировании. Для бронирования в ноябре вам необходимо: где дата начала между «2015-11-01» и «2015-12-01» или дата окончания между «2015-11-01» и «2015-12-01». Вы можете попробовать использовать union, чтобы посмотреть, сможете ли вы улучшить скорость.   -  person Tim3880    schedule 13.05.2015
comment
Бронирование с использованием ноября может начаться до ноября и закончиться после ноября. Когда присутствует индекс на startdate, enddate, id, он не используется y mysql (или любой другой sql, который я тестировал).   -  person Rafael    schedule 13.05.2015


Ответы (2)


Это сложная проблема. Нет INDEX подойдет.

Однако позвольте мне наметить обходной путь, который включает дополнительную таблицу и некоторую дополнительную обработку.

  1. Выберите период времени, например «месяц» (как в вашем примере).
  2. Создайте еще одну таблицу с ПЕРВИЧНЫМ КЛЮЧОМ (месяц, идентификатор), где id - идентификатор бронирования. (Не думаю, что нужны какие-то другие столбцы.)
  3. Для каждой записи (в вашей текущей таблице) заполните новую таблицу одной или несколькими строками, используя резервирование id и месяцы, которые оно частично или полностью охватывает.
  4. «Какие бронирования используют номера в ноябре» должно быть довольно просто и очевидно.
  5. «При бронировании номеров с 28 апреля по 4 мая нужно искать идентификаторы как за апрель, так и за май, а затем проверять фактические даты начала и окончания, чтобы отфильтровать те, которые не подходят.

Если вы используете «неделя» (вместо «месяц»), в новой таблице будет больше строк, но селективность лучше - трудно сказать, что лучше в долгосрочной перспективе.

Итак, запрос выглядит примерно так. (Я предполагаю, что от $ start_date до $ end_date указаны даты DATE и включительно.)

SELECT ...
    FROM new_table AS n
    JOIN bookings AS b USING(id)
    WHERE start_date <= n.month + INTERVAL 1 MONTH
      AND n.month <= $end_date
      AND ... same stuff for b. ...
person Rick James    schedule 12.05.2015
comment
Этот вариант звучит неплохо. Я скоро протестирую и опубликую здесь результаты. - person Rafael; 13.05.2015

Эти тесты проводились в: php 5.4 / MySql 5.5.42 Dual SSD Raid-1.

Резервирование столов с почти 2 миллионами записей и неделями (более 2 миллионов записей, потому что некоторые заказы охватывают более 2 недель)

CREATE TABLE `bookings` (
  `id` int(11) NOT NULL AUTO_INCREMENT, ## id is booking number.
  `start_date` date DEFAULT NULL,
  `end_date` date DEFAULT NULL,
  /* .  . . . . .  Rest of booking's info */
  PRIMARY KEY (`Id`),
  KEY `st_end` (`start_date, end_date`),
  KEY `end_date` (`end_date`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8  COMMENT='A record for each booking';


 CREATE TABLE `weeks` (
   `Id` int(11) NOT NULL AUTO_INCREMENT,
   `bk_nr` int(11) DEFAULT NULL,
   `week` int(11) DEFAULT NULL,
   PRIMARY KEY (`Id`),
   KEY `week` (`week`),
   KEY `bk_nr` (`bk_nr`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 
 COMMENT='Records indicate that booking (bk_nr) touches week number (week)';

Each booking may have several records in "weeks". One for each week that it touches.

Неделя была создана на php с использованием даты («oW»). Он создает год-неделю, например: для 2015-03-01: 201509 (2015 год, неделя 09).

Результаты теста. Поиск бронирований, использующих любой день марта 2015 года. Сначала худшее.

0,56 секунды SELECT DISTINCT c.* FROM bookings c WHERE c.d_start_date <= "2015-03-31" AND c.d_end_date >= "2015-03-01" AND c.id in ( SELECT w.bk_nr FROM weeks w
WHERE w.week between 201509 AND 201514 );

0,14 секунды SELECT DISTINCT c.* FROM bookings c, weeks w WHERE c.id = w.bk_nr AND w.week between 201509 AND 201514 AND c.d_start_date <= "2015-03-31" AND c.d_end_date >= "2015-03-01";

И победителем становится . . . .

Лучшее: 0,1 секунды SELECT DISTINCT * FROM bookings WHERE start_date <= "2015-03-31" AND end_date >= "2015-03-01";

Похоже, MySql улучшился по сравнению с моими предыдущими тестами (выполненными в mysql 4.x), и теперь он знает, как эффективно обрабатывать этот тип запросов.

Не нужно этому помогать. (Время измерялось простым "SELECT now ()" до и после каждого запроса.) Спасибо, Рик Джеймс, за вашу помощь.

person Rafael    schedule 14.05.2015
comment
В MySql 4.x 2 диска Sata Raid-10. Лучшее время: 49 секунд. - person Rafael; 15.05.2015
comment
Еще одно отличие. Старые столы были MyIsam. Новые таблицы - это InnoDB. - person Rafael; 18.05.2015