Как и что сохранять для данных графика работы?

Тема в разделе "Базы данных", создана пользователем danneo, 16 окт 2015.

Модераторы: latteo
  1. danneo

    danneo Честный

    Регистр.:
    13 ноя 2007
    Сообщения:
    1.453
    Симпатии:
    113
    Задача:
    1. Нужно в каталоге фирм сделать график работ. На PHP + MySQL.
    2. График с часами и минутами
    3. Делать выборку, кто работает в данное время, еще будет работать 2 часа и т.д.
    4. Ну и у фирмы тоже выводить ее график работы.

    Встал вопрос в актуальности информации. Где-то на сайтах есть разбивка по дням, где-то на будни и суб., воскр.. Также стоит учесть обед. У кого-то пятница укороченные дни.
    Вот и думаю, вроде как получается, нужна разбивка по дням.

    Также встает вопрос про интервалы работы. Как правило, это два интервала на один день недели: рабочее время и обед.

    Вопросы:
    1. Какие данные лучше всего собирать? Почему?
    2. Какую структуру для MySQL?

    добавилась еще одна проблема: некоторые фирмы работают с вечера, например, с 20 до 3 или до 7 утра. Как делать выборку в таком случае?
     
    Последнее редактирование модератором: 18 окт 2015
  2. EnGeLs

    EnGeLs Постоялец

    Регистр.:
    30 апр 2008
    Сообщения:
    87
    Симпатии:
    21
    Думаю самое универсальное занести данные об интервалах работы в отдельную таблицу:
    ||id_company||date_start||date_end||

    Хранить в формате типа такого

    ||1||2000-01-01 08:00:00||2000-01-01 13:00:00||
    ||1||2000-01-01 14:00:00||2000-01-01 20:00:00||
    ||1||2000-01-02 08:00:00||2000-01-02 13:00:00||
    ||1||2000-01-02 14:00:00||2000-01-02 20:00:00||
    ||2||2000-01-01 20:00:00||2000-01-02 03:00:00||
    ||2||2000-01-02 04:00:00||2000-01-02 08:00:00||

    А выдергивать запросом по дню недели , часу и минуте
    т.е. что то типа where DAYOFWEEK(NOW()) between DAYOFWEEK(date_start) AND DAYOFWEEK(date_end)
    AND "текущий час" между "час даты начала" и "час даты окончания" AND "так же с минутами"
    Возможно, не очень быстро, но с индексами должно быть нормально.

    Что касается 2000-01-01 это абстрактная дата которая может быть любой, главное чтобы дни недели у них были соответствующие, всего 7 (ну или 5 - сколько дней в неделю работает)
    Вопрос можно усложнить если надо учитывать нерабочие дни, Новогодние праздники например.
    Тогда можно добавить признак что в этом интервале работает/не работает и ставить реальные год месяц и день, но запрос придется переписать.
     
  3. Black Hat

    Black Hat Постоялец

    Регистр.:
    15 май 2015
    Сообщения:
    120
    Симпатии:
    80
    Значит, идея такая - храним "типичный" график работы. Как не типичный - напишу в конце.
    Итак, типичный график: начало и конец работы, начало и конец перерыва.

    Запрос по работе довольно тривиальный: WHERE (время) BETWEEN (начало и конец) AND (время) NOT BETWEEN (перерыв начало-конец).
    С днями недели также: DAYOFWEEK(NOW()) BETWEEN (первый день работы и последний день работы)...
    Только первый и последний день работы сразу хранить в виде цифры 0..6, чтобы по каждой строке не пересчитывать - это долго.
    И индексы тут не помогут - с функциями по столбцу они не работают.

    Если взять, скажем, среду, 11 утра - большинство фирм реально будет работать. И этот запрос реально мало что отсеит.
    Поэтому, наверное, там будут доп условия - например ищем больницу в среду, 11 утра. Тогда селективность по "больнице" будет довольно высока.

    В итоге имеем, допустим, 1 тыс записей. По графику работы, допустим, срежутся еще 20% (довольно популярное время выбрали), на выходе 800.

    А теперь то, что можно назвать NoSQL-подходом.

    1. Некоторые фирмы имеют два перерыва.
    2. Некоторые конкретно не работают в какой-то день (1 апреля, например).
    3. Некоторые не работают на Пасху (этот день не имеет строгой даты).

    Теперь как это разруливать.
    Итак, первичный SQL-отсев оставил нам 800 записей. Их все реально получить и в PHP обработать - лучше сделать замыканиями. Это будет довольно быстро.
    В БД заводим еще один столбец `details`, в нем храним в виде json_encode() или serialize() данные, например такие (пишу в виде JSON)

    Код:
    {
      "second_break": {
        "begin": "8-00",
        "end": "8-30",
        "comment": "дуратский второй перерыв на полчаса"
      },
      "exclude_days": {
        "days": ["01.04", "02.04"],
        "comment": "не работаем на день дурака и на следующий"
      },
      "custom_function": {
        "name": "pasxa",
        "comment": "не работаем на пасху"
      }
    }
    
    Теперь перебираем все записи через foreach (или array_filter()) и разруливаем: если есть "second_break" - запускаем функцию (анонимную, либо метод класса), которая возвращает
    true/false - т.е. прошла запись проверку или нет. То есть фильтруем более точно. Где надо - рассчитываем Пасху, проверяем на день дурака и т.п.

    Если используйте PostgreSQL, то в нем есть уже есть фильтрация по json! Также все это можно поднять на MondoDB и использовать обычный фильтр плюс мап-редукцию (то что написано выше на PHP, толко на JS).

    Проблема "с 20 до 3" - есть такая загвоздка, да. Потом напишу.
     
  4. BestProxies

    BestProxies Создатель

    Регистр.:
    15 авг 2012
    Сообщения:
    14
    Симпатии:
    3
    Я бы сделал три таблицы:
    1. Основной график (id фирмы, день недели, час начала рабочего дня, час окончания рабочего дня)
    2. Перерывы (id фирмы, день недели, час начала перерыва, час окончания перерыва)
    3. Производственный календарь (timestamp), содержит даты нерабочих дней
    Если расписания начала/окончания рабочего дня или перерыва должно включать минуты, нужно добавить соответствующие поля.

    Код:
    HOUR(NOW()) BETWEEN `start_hour` AND IF(`start_hour` <= `end_hour`, `end_hour`, 23)
    OR
    HOUR(NOW()) BETWEEN IF(`start_hour` > `end_hour`, 0, `start_hour`) AND `end_hour`
    
     
  5. EnGeLs

    EnGeLs Постоялец

    Регистр.:
    30 апр 2008
    Сообщения:
    87
    Симпатии:
    21
    Предположим, что в пятницу магазин работает с 20:00 до 4 утра следующего дня. Человек заходит на сайт в субботу в 1 утра, т.е. в запросе день недели уже будет суббота а не пятница, и что выдаст данный запрос?
     
  6. Black Hat

    Black Hat Постоялец

    Регистр.:
    15 май 2015
    Сообщения:
    120
    Симпатии:
    80
    По моей схеме - ставим рабочий день субботу (хотя, он работает в субботу с 00:00 до 4 утра), предварительная выборка отрабатывает, далее - мап-редукция средствами PHP
    В субботу магазин все же работает, говоря технически.
    Какое количество записей предполагается?
     
  7. EnGeLs

    EnGeLs Постоялец

    Регистр.:
    30 апр 2008
    Сообщения:
    87
    Симпатии:
    21
    Мой ответ относится все же к варианту предложенному BestProxies.
    Что касается вашего варианта, я бы не стал использовать NoSQL подход в SQL базе. По нику понятно что топикстартер разработчик системы DanneoCMS. Пользователи движка врядли будут ставить дополнительно PostgreSQL или MongoDB , потому что во первых для этого надо иметь VDS или дедик, во вторых это требует определенных навыков, а среднестатистический пользователь готовой CMS это не осилит.
    Если уж говорить о производительности, то можно создать дополнительную кэширующую таблицу вида
    ||день недели||час и минута||ид компаний работающих в этот день недели час и минуту через запятую||
    И при добавлении новой компании или режима работы перезаписывать эту таблицу, в ней будет всего 24*60*7 = 10080 записей, тут даже индексы не нужны будут.
    А основную изначальную таблицу использовать для формирования таблицы всего графика работы конкретной компании, там по ид компании тоже моментально будет происходить выборка.
     
    Последнее редактирование: 22 окт 2015
  8. danneo

    danneo Честный

    Регистр.:
    13 ноя 2007
    Сообщения:
    1.453
    Симпатии:
    113
    Не DanneoCMS, но в принципе, полностью согласен.
    Как правило, выборка будет происходить не по конкретным часам, типа, покажите мне фирмы, которые работают в понедельник с трех до пяти, а по запросу "которые работают еще 2 часа". Это значит, что известно: день недели и окончание работы, а время обеда и начало работы не известно. Получается, что нужно найти интервал работы по дню недели, где выбранный (time() + 2) >= end_time окончания работы в таблице, при этом нет данного времени в интервале перерывов.
    Не понятно, как формировать запрос с учетом не только часов, но и минут. Переводить в минуты? Или искать часы в часах, минуты в минутах?
    И не раскрыта тема по запросу (как он выглядит), когда время работы заполночь.
    Про праздники, мне кажется, вообще нереализуемо. Любая фирма может работать в праздник и по любому графику. Это уже календарь целый нужно на каждого :)