Как бы вы хранили и запрашивали часы работы? - PullRequest
4 голосов
/ 27 сентября 2008

Мы создаем приложение, которое хранит «часы работы» для различных предприятий. Какой самый простой способ представить эти данные, чтобы вы могли легко проверить, открыт ли элемент?

Некоторые опции:

  • Сегментируйте блоки (каждые 15 минут), которые можно пометить как «открытые / закрытые». Проверка включает в себя определение, установлен ли бит «открыто» на желаемое время (немного похоже на расписание поездов).
  • Сохранение списка временных диапазонов (с 11:00 до 17:00, 17:00 и т. Д.) И проверка того, попадает ли текущее время в какой-либо указанный диапазон (это то, что делает наш мозг при анализе приведенных выше строк).

Есть ли у кого-нибудь опыт хранения и запроса информации о расписании и какие-либо советы, чтобы дать?

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

Ответы [ 10 ]

5 голосов
/ 27 сентября 2008

хранить каждый непрерывный блок времени как время начала и продолжительность; это облегчает проверку, когда часы пересекают границы даты

если вы уверены, что часы работы никогда не пересекут границы дат (т.е. никогда не будет распродажи в течение всей ночи или 72-часового марафона и т. Д.), Тогда времени начала / окончания будет достаточно

3 голосов
/ 27 сентября 2008

Наиболее гибким решением может быть использование битового подхода. В неделю 168 часов, поэтому есть 672 15-минутных периода. Это всего лишь 84 байта места, что должно быть терпимо.

2 голосов
/ 27 сентября 2008

Я бы использовал такую ​​таблицу:

BusinessID | weekDay | OpenTime | CloseTime 
---------------------------------------------
     1          1        9           13
     1          2        5           18
     1          3        5           18
     1          4        5           18
     1          5        5           18
     1          6        5           18
     1          7        5           18

Здесь у нас есть бизнес, который работает по обычному графику с 5 до 6, но по воскресеньям укорочено.

Запрос на открытие будет (psuedo-sql)

SELECT @isOpen = CAST
   (SELECT 1 FROM tblHours 
       WHERE BusinessId = @id AND weekDay = @Day 
       AND CONVERT(Currentime to 24 hour) IS BETWEEN(OpenTime,CloseTime)) AS BIT;

Если вам нужно сохранить пограничные случаи, просто наберите 365 записей, по одной в день ... на самом деле это не так уж много, поместите индекс в столбец дня и столбец businessId.

Не забудьте сохранить часовой пояс предприятия в отдельной таблице (нормализовать!) И выполнить преобразование между вашим временем и временем перед выполнением этих сравнений.

1 голос
/ 19 ноября 2013

ОК, я добавлю это, чтобы оно того стоило.

Мне нужно разобраться с несколькими вещами.

  • Fast / Performant Query
  • Любые приращения времени, 21:01, 12:14 и т. Д.
  • Международный (?) - не уверен, что это проблема даже с часовыми поясами, по крайней мере, в моем случае, но кто-то более опытный здесь, не стесняйтесь звонить в
  • Открыть - Закрыть на следующий день (открыть в полдень, закрыть в 2:00)
  • Несколько раз в день
  • Возможность переопределять определенные дни (праздники, что угодно)
  • Возможность повторения переопределений
  • Возможность запроса в любой момент времени и открытия бизнеса (сейчас, в будущем, в прошлом)
  • Возможность легко исключать результаты закрытия предприятий в ближайшее время (фильтровать предприятия, закрывающиеся за 30 минут, вы не хотите, чтобы ваши пользователи были тем парнем, который появляется за 5 минут до закрытия в индустрии продуктов питания и напитков)

Мне нравится множество представленных подходов, и я заимствую у некоторых из них. На моем веб-сайте, проекте, что бы мне ни пришлось принять во внимание, у меня могут быть миллионы компаний, и некоторые из подходов, которые мне здесь представляются, не подходят мне лично.

Вот что я предлагаю для алгоритма и структуры.

Мы должны сделать некоторые конкретные предположения по всему миру, в любом месте, в любое время: В неделе 7 дней. В один день 1440 минут. Существует конечное число перестановок минут открытия / закрытия, которые возможны.

Не конкретные, а достойные предположения: Многие перестановки открытых / закрытых минут будут распределены между предприятиями, уменьшая общее количество перестановок, фактически сохраненных В моей жизни было время, когда я мог легко рассчитать фактические возможные комбинации для этого подхода, но если кто-то сможет помочь / посчитает, что это будет полезно, это было бы здорово.

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

Таблица HoursOfOperations

ID | OPEN (минута дня) | ЗАКРЫТЬ (минута дня)


1 | 360 | 1020 (пример: 9:00 - 17:00)

2 | 365 | 1021 (пример: крайний случай 9:05 AM - 5:01 PM (weirdos))

и т.д.

HoursOfOperations не заботится о том, какие дни, просто открываются и закрываются и уникальны. Для каждой комбинации открытия / закрытия может быть только одна запись. Теперь, в зависимости от вашей среды, вся эта таблица может быть кэширована или кэширована для текущего часа дня и т. Д. В любом случае вам не нужно запрашивать эту таблицу для каждой операции. В зависимости от вашего решения для хранения, я предполагаю, что каждый столбец в этой таблице будет проиндексирован для повышения производительности. С течением времени эта таблица, вероятно, имеет экспоненциально обратную вероятность INSERT (s). Однако на самом деле работа с этой таблицей в основном должна быть оперативной операцией (ОЗУ).

Business2HoursMap

Примечание: в моем примере я храню «День» как поле / столбец битового флага. Во многом это связано с моими потребностями и развитием перечислений LINQ / Flags в C #. Ничто не мешает вам расширить это до 7 битных полей. Оба подхода должны быть относительно похожими как в логике хранения, так и в подходе к запросу.

Еще одно примечание: я не вхожу в аргумент семантики "каждой таблице нужен столбец идентификатора PK", пожалуйста, найдите другой форум для этого.

BusinessID | HoursID | День (или, если вы предпочитаете, разделить на: BIT понедельник, BIT вторник, ...)


1 | 1 | 1111111 (этот бизнес открыт 9-5 каждый день недели)

2 | 2 | 1111110 (этот бизнес открыт 9:05 - 5:01 М-СБ (понедельник = день 1)

Причина, по которой это легко запросить, состоит в том, что мы всегда можем довольно легко определить MOTD (Минута дня), к которому мы стремимся. Если я хочу узнать, что завтра открывается в 17:00, я беру все идентификаторы HoursOfOperations, ГДЕ Close> = 1020. Если я не ищу временной диапазон, Open становится незначительным. Если вы не хотите, чтобы предприятия закрывались в следующие полчаса, просто измените время прихода (ищите 17:30 (1050), а не 17:00 (1020). Второй запрос, естественно, будет «дать мне все дело с HoursID IN (1, 2, 3, 4, 5) и т. Д. Это, вероятно, должно поднять красный флаг, поскольку существуют ограничения для этого подхода. Тем не менее, если кто-то может ответить на реальный вопрос о перестановках, приведенный выше, мы можем убрать красный флаг. Представьте, что нам нужны только возможные перестановки на любой стороне уравнения одновременно, открытая или закрытая.

Учитывая, что наша первая таблица кэширована, это быстрая операция. Вторая операция - запрос этой потенциально большой строки таблицы, но мы ищем очень маленькие (SMALLINT), мы надеемся, проиндексированные столбцы.

Теперь вы можете видеть сложность кода. Я нацеливаюсь в основном на бары в моем конкретном проекте, поэтому будет весьма безопасно предположить, что у меня будет значительное количество предприятий с часами, такими как «11:00 AM - 2:00 AM (следующий день)». В действительности это будет 2 записи как в таблицу HoursOfOperations, так и в таблицу Business2HoursMap. Например. бар, открытый с 11:00 до 2:00, будет иметь две ссылки на таблицу HoursOfOperations 660 - 1440 (11:00 - полночь) и 0 - 120 (полночь - 2:00). Эти ссылки будут отражены в фактических днях в таблице Business2HoursMap в виде 2 записей в нашем упрощенном случае: 1 запись = ссылка на все дни # 1, другая ссылка на все дни # 2. Надеюсь, это имеет смысл, это был долгий день.

Переопределение в особые дни / праздники / что угодно. Переопределения по своей природе основаны на дате, а не на дне недели. Я думаю, что именно здесь некоторые подходы пытаются засунуть пресловутый круглый колышек в квадратное отверстие. Нам нужен еще один стол.

HoursID | BusinessID | День | Месяц | Год

1 | 2 | 1 | 1 | NULL

Это, безусловно, может стать более сложным, если вам нужно что-то вроде "каждый второй вторник, эта компания ловит рыбу в течение 4 часов". Однако то, что это позволит нам сделать довольно легко, это разрешить 1 - переопределения, 2 - разумные повторяющиеся переопределения. НАПРИМЕР. Если год равен NULL, то каждый год в новогодний день этот чудной бар открыт с 9:00 до 17:00, что соответствует приведенным выше примерам данных. То есть - Если год был установлен, это только на 2013 год. Если месяц равен нулю, это каждый первый день месяца. Опять же, это не будет обрабатывать каждый сценарий планирования только по столбцам NULL, но теоретически вы можете обрабатывать практически все, полагаясь на длинную последовательность абсолютных дат.

Опять же, я бы кэшировал эту таблицу на скользящей основе. Я просто не могу реально увидеть строки для этой таблицы в однодневном снимке очень большого размера, по крайней мере, для моих нужд. Я бы сначала проверил эту таблицу, так как она корректна, переопределил бы и сохранил бы запрос к намного большей таблице Business2HoursMap на стороне хранилища.

Интересная проблема. Я действительно удивлен, что это первый раз, когда мне действительно нужно обдумать это. Как всегда, я очень заинтересован в различном понимании, подходах или недостатках моего подхода.

1 голос
/ 27 сентября 2008

Чтобы добавить к тому, что сказал Джонатан Холланд , я бы разрешил несколько записей за один и тот же день.

Я бы также допустил десятичное время или другой столбец для минут.

Почему? во многих ресторанах и некоторых компаниях, а также во многих компаниях по всему миру обеденные и / или дневные перерывы. Кроме того, многие рестораны (2, о которых я знаю, около моего дома закрываются в нечетное время, не превышающее 15 минут). Каждый закрывается в 9:40 вечера по воскресеньям, а другой - в 1:40 утра.

Существует также проблема праздничных часов, таких как, например, закрытие магазинов рано в день благодарения, поэтому вам необходимо иметь переопределение на основе календаря.

Возможно, что можно сделать, это открыть дату / время, закрыть дату и время, например:

businessID  | datetime              | type
==========================================
        1     10/1/2008 10:30:00 AM    1
        1     10/1/2008 02:45:00 PM    0
        1     10/1/2008 05:15:00 PM    1
        1     10/2/2008 02:00:00 AM    0
        1     10/2/2008 10:30:00 AM    1

и т.д.. (тип: 1 открыт и 0 закрыт) * ​​1014 *

И все дни в ближайшие 1 или два года рассчитать заранее за 1-2 года. Обратите внимание, что у вас будет только 3 столбца: int, дата / время / бит, поэтому потребление данных должно быть минимальным.

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

Он также заботится о пересечении в полночь, а также о 12/24 часовых преобразованиях.

Это также не зависит от часового пояса. Если вы сохраняете время начала и продолжительность, когда вы вычисляете время окончания, будет ли ваша машина давать вам время, отрегулированное TZ? Это то, что вы хотите? Больше кода.

для запроса статуса «открыто-закрыто»: запросить дату и время, о которых идет речь,

select top 1 type from thehours where datetimefield<=somedatetime and businessID = somebusinessid order by datetime desc

тогда посмотрите на "тип". если один, он открыт, если 0, он закрыт.

PS: я был в рознице 10 лет. Так что я знаком с проблемами сумасшедших часов малого бизнеса.

1 голос
/ 27 сентября 2008

Я думаю, что лично я бы пошел на время начала + окончания, поскольку это сделало бы все более гибким. Хороший вопрос будет: какова вероятность того, что размер блока изменится в определенный момент? Затем выберите решение, которое наилучшим образом соответствует вашей ситуации (если оно может измениться, я определенно выберу время).

Вы можете хранить их как интервал времени и использовать сегменты в своем приложении. Таким образом, вы легко вводите данные с помощью блоков, сохраняя гибкость в изменении хранилища данных.

0 голосов
/ 27 сентября 2008

Как насчет этого:

Таблица часов магазина

Business_id (int)
Start_Time (time)
End_Time (time)
Condition varchar/string
Open bit

«Условие» - это лямбда-выражение (текст для предложения «где»). Построить запрос динамически. Поэтому для конкретного бизнеса вы выбираете все времена открытия / закрытия

Let Query1 = select count(open) from store_hours where @t between start_time and end_time and open  = true and business_id = @id and (.. dynamically built expression)

Let Query2 = select count(closed) from store_hours where @t between start_time and end_time and open = false and business_id = @id and (.. dynamically built expression)

Итак, в конце вы хотите что-то вроде:

select cast(Query1 as bit) & ~cast(Query2 as bit)

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

Теперь вам просто нужен дружественный интерфейс, который может генерировать ваши предложения where (лямбда-выражения) для вас.

Единственный другой угловой случай, о котором я могу подумать, это то, что происходит, если магазин открыт с 7:00 до 2:00 в один день, но закрывается в 11:00 следующего дня. Ваша система должна справиться и с этим, умно разделив время между двумя днями.

0 голосов
/ 27 сентября 2008

Я бы немного подумал об этих пограничных случаях прямо сейчас, потому что они собираются сообщить, есть ли у вас базовая конфигурация плюс оверлей или полное статическое хранение времени открытия или что-то еще.

Существует так много исключений - и на регулярной основе (например, снежные дни, нерегулярные праздники, такие как Пасха, Страстная пятница), что, если ожидается, что это будет надежным представлением реальности (в отличие от хорошей догадки), Мне нужно будет решить это довольно скоро в архитектуре.

0 голосов
/ 27 сентября 2008

Здесь, конечно, нет необходимости экономить память, но, возможно, нужен чистый и понятный код. ИМХО, «немного трюкать» не так.

Нам нужен контейнер для набора, который содержит любое количество уникальных элементов и может быстро и легко определить, является ли элемент членом или нет. Установка требует осторожности, но при обычном использовании одна строка понятного кода определяет, открыты вы или нет

Концепция: Присвойте индексный номер каждому 15-минутному блоку, начиная, скажем, с полуночного воскресенья.

Инициализировать: Вставьте в набор порядковый номер каждого 15-минутного блока, когда вы открыты. (При условии, что вы открыты меньше часов, чем вы закрыты.)

Использование: Вычтите из интересного времени, в минутах, полуночи предыдущего воскресенья и разделите на 15. Если это число присутствует в наборе, вы открыты.

0 голосов
/ 27 сентября 2008

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

Любая другая система (например, диапазоны) будет действительно раздражать, когда вы пересекаете границу полуночи.

Что касается того, как вы храните их, в C ++ битовые поля, вероятно, будут лучше всего. В большинстве других языков массив может быть лучше (много потерянного пространства, но он будет работать быстрее и его будет легче понять).

...