Как SQL-запросом найти магазины, работающие в час ночи понедельника по времени Владивостока?



Коллеги, пардон, но всю голову уже сломали: представим, что кастомер вводит в админке сайта "время работы магазина в воскресенье 11-02 по времени Москвы". Задача - SQL-запросом найти магазины, работающие в час ночи понедельника по времени Владивостока.Как?UPD: пожалуйста, не пишите "храни всё в UTC". Это очевидно, но это вообще не решает задачу (см. комменты ниже).
967   79  

Comments

  1. Alexander Orlovsky
    Alexander Orlovsky 5 лет назад
    не путать данные и их представление
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Отличный совет, но он отвечает на исходный вопрос чуть менее чем никак.
    • Alexander Orlovsky
      Alexander Orlovsky 5 лет назад
      Григорий Добряков а при чём тут HL?
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Alexander Orlovsky при том, что магазинов много и вычисляться должно очень быстро.
  2. Григорий Добряков
    Григорий Добряков 5 лет назад
    Для простоты предположим, что мы ничем не ограничены в реализации. База постгрес.
    • Alexander Orlovsky
      Alexander Orlovsky 5 лет назад
      храните utc (данные), при запросах и отображениях результатов учитывайте часовой пояс.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Alexander Orlovsky пожалуйста, вместо умных советов напишите SQL-запрос.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      По крайней мере начните писать. Может быть, увидите, что это нихрена не просто, как кажется.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Мне пока показали вот это. Как-то ни разу не тривиально. http://stackoverflow.com/.../perform-this-hours-of...
    • Alexander Orlovsky
      Alexander Orlovsky 5 лет назад
      Григорий Добряков я понял проблему, но ИМХО тут вё-равно неправильное место для таких вопросов
    • Alexander Orlovsky
      Alexander Orlovsky 5 лет назад
      есть sql.ru например или stackoverflow
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Alexander Orlovsky ну что ж делать, если модератор потрёт мой пост, я не обижусь.
    • Alexander Orlovsky
      Alexander Orlovsky 5 лет назад
      Григорий Добряков время работы магазина в разные дни разные? вообще без вводных данных на ваш ответ ответить можно только если случайно (на опыте или интуиции)
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Alexander Orlovsky я побоялся, что большой пост будет лень читать. В разные дни - разные. Есть ещё обед. Есть ещё праздничные дни в каждой стране свои. Но для начала хотя бы с воскресеньем разобраться
    • Роман Жуков
      Роман Жуков 5 лет назад
      Какое количество магазинов-то?
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Роман Жуков не очень много, десятки тысяч, может быть сотни. Но явно не больше.
    • Николай Самохвалов
      Николай Самохвалов 5 лет назад
      Alexander Orlovsky хранить в UTC – плохой совет. Храните в timestamptz и работайте с таймзонами в Постгресе, все инструменты для этого есть
    • Alexander Orlovsky
      Alexander Orlovsky 5 лет назад
      Николай Самохвалов Если не хочется себя приколотить гвоздями к Pg, то можно без timestamptz обойтись (посмотрел – это UTC на стероидах). Но вообще спасибо, не знал про timestamptz, хотя и догадывался что в Pg есть всё
  3. Владислав Ярмак
    Владислав Ярмак 5 лет назад
    Попробуйте представить время работы магазина в виде набора в виде сочетаний часа и дня недели, в которые он работает. То есть полночь понедельника, час ночи понедельника и так далее. Получится битовая строка, по которой можно сделать AND. https://www.postgresql.org/.../functions-bitstring.html
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Да, это было у нас решение номер два - представить неделю как прямую ось времени на 8 дней. Почему на 8 - потому что хвост воскресенья это второй понедельник.
    • Владислав Ярмак
      Владислав Ярмак 5 лет назад
      Григорий Добряков Время вам так и так потребуется приводить к часы UTC, иначе всё будет очень запутанно. Тогда и хвоста не будет. Если сделать хотя бы с градацией 15 минут, то получится 84 байта, что вполне терпимо.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Владислав Ярмак будет хвост, к сожалению... Ничто не мешает жителю Москвы ввести "воскресенье 23-18", и при приведении к UTC это будет "20-15", то есть мы всё равно заехали на понедельник.
    • Владислав Ярмак
      Владислав Ярмак 5 лет назад
      Григорий Добряков А это вы уже должны закруглять интервал при составлении поисковой маски
    • Андрей Якубовский
      Андрей Якубовский 5 лет назад
      Время в любом случае надо привести либо в UTC либо требуемую TZ, а потом попробовать представить День недели, Часы, Минуты как число DOWHHMM и сравнить с требуемым занчением
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Владислав Ярмак то есть принудительно приводить хвост воскресенья к началу недели, к понедельнику? Например если юзер ввёл 11-02, то мы должны записать 11-24 воскресенья и 00-02 понедельника? А как потом восстановить обратно, чтобы в интерфейсе показать ожидаемое юзером?
    • Владислав Ярмак
      Владислав Ярмак 5 лет назад
      Григорий Добряков Да. Если запрос был "работает ли магазин целиком в заданный интервал времени", то нужно сравнивать результат AND строки с самой маской и при равенстве ответ будет просто "да". Если запрос был "в какие часы часы работает магазин из указанного интервала", то нужно собрать все результаты AND, которые не равны строке с нулями и затем выводить интервалы от текущего момента времени, сопоставляя с датами на календаре. То есть если юзер делает запрос в в среду "в какие дни работает магазин с понедельника по пятницу", разумно выводить ближайшие даты на неделе, которые находятся не в прошлом.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Владислав Ярмак если я порежу интервал на два дня, как потом восстановить обратно то время которое ввёл администратор магазина? Чтобы в интерфейсе показать ожидаемое администратором "11-02"?
    • Владислав Ярмак
      Владислав Ярмак 5 лет назад
      Григорий Добряков После получения вектора, когда магазин работает, нужно всегда сначала переводить каждый установленный бит в соответствующий день недели и час, понятный пользователю, а затем группировать в интервалы по дням. Если пользователю лучше понятно по календарным дням недели, а магазин работает с 10 утра до 2 часов ночи уже следующего дня, то так и писать: понедельник 10-24, вторник 00-02. Если все магазины точно закрываются на ночь, а пользователю понятнее вывод "понедельник: с 10 утра до двух ночи", то агрегировать в интервал так: каждый интервал длительностью меньше 24х часов получает тот день недели, в который начался.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Владислав Ярмак нет, попытаюсь объяснить ещё раз: вот сидит пользователь, он администратор магазина. Он поставил в интерфейсе в настройках "воскресенье 11-02". Вот такое бизнесовое требование - уметь ставить время в таком виде.
    • Владислав Ярмак
      Владислав Ярмак 5 лет назад
      Григорий Добряков Я рассмотрел этот случай после второго "Если"
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Владислав Ярмак окей, хорошее решение, спасибо!
    • Владимир Кольцов
      Владимир Кольцов 5 лет назад
      вы меня, конечно, извините, но как воскресенье москвы превратилось во вторник камчатки? в этой стране в лучшие времена было 13часовых поясов, сейчас всего 10, не 25!ну а дату 1) можно кодом перевести 2) сделать хитрую рожу и понаставить серверов с разными локалями, там само переведётся при utc+tz
  4. Григорий Соколик
    Григорий Соколик 5 лет назад
    SELECT ... FROM stores WHERE \(gmt_start_hour <= ?AND consists_nght) OR (gmt_end_hour >= ?AND consists_night) OR (gmt_start_hour <= ?AND gmt_end_hour >= ?)
  5. Григорий Соколик
    Григорий Соколик 5 лет назад
    Нужно действительно хранить время в одном ЧП + добавить флаг "работает ли по ночам"
  6. Григорий Соколик
    Григорий Соколик 5 лет назад
    А, еще дни недели.
  7. Григорий Соколик
    Григорий Соколик 5 лет назад
    SELECT ... FROM store sJOIN work_h_per_d w ON w.store_id = s.id AND w.week_day = ?WHERE(gmt_start_hour <= ?AND consists_nght) OR (gmt_end_hour >= ?AND consists_night) OR (gmt_start_hour <= ?AND gmt_end_hour >= ?)
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Окей, вот я сижу в таймзоне Москвы +03:00, ввожу время работы "воскресенье 23-18", оно приводится к UTC как "20-15", в базу кладётся gmt_start_hour:20 и gmt_end_hour:15.
    • Григорий Соколик
      Григорий Соколик 5 лет назад
      Да, забавно.
    • Григорий Соколик
      Григорий Соколик 5 лет назад
      Ок.
    • Григорий Соколик
      Григорий Соколик 5 лет назад
      Тогда можно вообще забить на consist_night, если есть ночная работа -- писать 2 строки.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Григорий Соколик если я порежу интервал 11-02 на два дня (11-24 и 00-02), как потом восстановить обратно то время которое ввёл администратор магазина? Чтобы в интерфейсе показать ожидаемое администратором "11-02"?
    • Григорий Соколик
      Григорий Соколик 5 лет назад
      Тогда надо уточнять задачу: можно только 2 дня аффектить интервалом?
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Григорий Соколик ну мы для себя делаем, тут всё в наших руках. Скорее всего *только два*.
    • Григорий Соколик
      Григорий Соколик 5 лет назад
      Надо селектить все интервалы с дня до реквестируемого по день после (их должно быть в худшем случае 6 штук), аппликейшеном находить "сшитые" и выбирать нужный.
  8. Александр Лурье
    Александр Лурье 5 лет назад
    А зачем SQL? По-моему, у вас весь головняк из-за того, что в задаче лишнее требование. Загрузите время работы всех магазинов в память и посчитайте на нормальном языке. 100 тыс это же вообще ни о чем - на мобильнике и то больше памяти. Можно даже готовые предпосчитанные данные по времени работы хранить одним большим блобом и пересчитывать, когда данные по магазинам изменяются в админку. А если нужно вообще ультра-быстро, что даже блоб грузить из базы или кэша дорого, напилите микросервис, который будет в памяти держать всё времена открытия и молниеносно отвечать на запросы.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Тоже вариант.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Александр Лурье (Alexander Lourier) вопрос скорее архитектурный - хочу советов как лучше сделать.
    • Александр Лурье
      Александр Лурье 5 лет назад
      Всё эависит от стека технологий.
    • Николай Самохвалов
      Николай Самохвалов 5 лет назад
      затем, что задача СУБД — не только хранить данные, но и делать операции с ними, хорошая СУБД делает это намного лучше других систем.
    • Александр Лурье
      Александр Лурье 5 лет назад
      Николай Самохвалов (Nikolay Samokhvalov), это же история о трейдофах. С одной стороны - СУБД уже есть, можно не городить огород и всё решить на чистом SQL - в системе меньше компонентов, меньше точек отказа - это клёво. С другой - сложность решения на SQL может оказаться запретительно высокой. Кто-то же должен будет потом это поддерживать - баги там фиксить, тормоза лечить, новые фичи добавлять. Не дай бог эксперт, кто это изначально написал, уволится.
  9. Андрей Овчаров
    Андрей Овчаров 5 лет назад
    Я бы всё же хранил всё в UTC. Общая система координат и единая точка отсчёта - это разумно. Запрос пользователя тоже привести к UTC.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Андрей Овчаров (Andrey Ovcharov) прочитайте, пожалуйста, предыдущие комментарии (например тред с Григорием Соколиком) - там я объясняю, что проблема не в этом.
    • Роман Жуков
      Роман Жуков 5 лет назад
      Grigoriy Dobryakov Правильно я понимаю, что проблема в том, что вы не можете придумать как хранить такие данные? Или под предложенный вариант хранения не можете придумать конвертер в слой представления? У меня вот такое ощущение сложилось.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Роман Жуков мы не можем выбрать нормальное решение в целом. И как хранить, и как вводить, и как выводить, и как искать. Каждый вариант несёт в себе те или иные костыли.
  10. Vitaly Levchenko
    Vitaly Levchenko 5 лет назад
    tszrange на каждый день недели, индекс — и всё легко и быстро и в бизнес-терминах, т.е. отображается и редактируется в том же виде.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Vitaly Levchenko как сохранить "воскресенье 11-02"? Это какой день недели?
    • Vitaly Levchenko
      Vitaly Levchenko 5 лет назад
      Григорий, неделю считаем с отсчётом от одного фиксированного дня. В таком случае день недели понятным образом интерпретируется, но в целом отсутствует в схеме.
  11. Александр Акафьев
    Александр Акафьев 5 лет назад
    Дело в том, что "хранить в UTC" - это не совсем выход, "понедельник, 11-02" не является датой-временем. Датой будет, например, 5 апреля 1971 года. С таким допущением задача решается, в общем, тривиально.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Александр Акафьев конечно, если переписать бизнесовое условие под возможности технологий, то любая задача решится тривиально
    • Александр Акафьев
      Александр Акафьев 5 лет назад
      Григорий Добряков, а разве у бизнесового требования есть условие на формат хранения ?
    • Александр Акафьев
      Александр Акафьев 5 лет назад
      Григорий Добряков, хранить все интервалы работы в общем надо. Потому что иначе вылезет вопрос "а как тогда чтоб работало с вечера пятницы до утра понедельника" ?
  12. Павел Чеботарев
    Павел Чеботарев 5 лет назад
    ... WHERE EXTRACT(DOW FROM timezone('Vladivostok/Vladivistok', opened_at)) == 1 AND ...
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Допустим, я сижу в таймзоне Москвы +03:00, ввожу время работы "воскресенье 23-18", оно приводится к UTC как "20-15", в базу кладётся opened_at:20 и closed_at:15.
    • Павел Чеботарев
      Павел Чеботарев 5 лет назад
      В базу придется класть полную дату, а не только время. То есть мы берем 1970-00-00 00:00:00, прибавляем к нему 6 DAYS 23 HOURS 18 MINUTES и в таком виде сохраняем в базу с текущей таймзоной. Тогда все должно правильно высчитываться.
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Pavel Chebotarev 23—18 это с 23 часов воскресенья до 18 часов понедельника. Как это хранить? Как по нему искать?
    • Павел Чеботарев
      Павел Чеботарев 5 лет назад
      А, я не так понял. Тогда все не так просто. Надо хранить интервал времени в пределах самой первой недели отсчета (если не пересекает полночь воскресенья, то это один интервал, если пересекает - то два интервала - от времени открытия до воскресенья 23:59:59 и от понедельника 0:0:0 до времени закрытия), а дальше пользовательский запрос превращать в такую же дату от 1970-1-1 0:0:0 и сверять с помощью BETWEEN с конвертацией в таймзону.
  13. Valeriy Valeev
    Valeriy Valeev 5 лет назад
    Хранить таймзону, день недели, время открытия и интервал. В запросе проверять WHERE now() BETWEEN последний понедельник 13:00 AND последний понедельник 13:00 + open_inteval.
    • Valeriy Valeev
      Valeriy Valeev 5 лет назад
      Чуть проще: время открытия - интервал от начала недели rel_open, время закрытия - интервал от времени открытия rel_close_in
  14. Николай Самохвалов
    Николай Самохвалов 5 лет назад
    Не надо изобретать велосипед.
    • Николай Самохвалов
      Николай Самохвалов 5 лет назад
      вот тут, например, на русском разжёвано https://habrahabr.ru/post/273177/
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Николай Самохвалов Эх... Окей, вот я сижу в таймзоне Москвы +03:00, ввожу время работы ночного клуба "воскресенье с 23 до 18", оно приводится к UTC как "20-15", в базу кладётся starts_at:20 и ends_at:15 с указанием таймзоны +03.
    • Николай Самохвалов
      Николай Самохвалов 5 лет назад
      Grigoriy Dobryakov прочитай статью ну или я сейчас освобожусь и покажу на твоём примере
    • Григорий Добряков
      Григорий Добряков 5 лет назад
      Николай Самохвалов лучше покажи. Потому что по всему треду выше, как только я объяснял про суть перехода через полночь, каждый начинал предлагать свои костыли.
  15. Андрей Григорьев
    Андрей Григорьев 5 лет назад
    SELECT s.id FROM shops sJOIN shop_works w ON w.shop_id = s.idWHERE w.from <= (10080 + 60 - 600 + s.tz) % 10080 AND (10080 + 60 - 600 + s.tz) % 10080 < w.to;
    • Андрей Григорьев
      Андрей Григорьев 5 лет назад
      Блин. С такой ужасной постановкой даже я в правильном решении ошибку сделал. Отрезки сохранять на интервале, и не двигать их за него.
  16. Alexandr Kakunov
    Alexandr Kakunov 5 лет назад
    Пусть в ваших сутках будет 48 часов. Это с некоторым дублированием решит проблему перехода через полночь.
  17. Igor Podlesny
    Igor Podlesny 5 лет назад
    В рассчётах переведите timestamp'ы в минуты (или, если хотите, секунды) с учётом дня недели. Ну, то есть, сначала уходим на GMT, например, потом используем недельную шкалу (и модульной арифметикой). То бишь, полночь понедельника это 86400, а вторника — 2*86400, ну и так далее.
  18. Акжан Абдулин
    Акжан Абдулин 5 лет назад
    select * from shops where work_hours_starts_with >= 20100707 +10'::timestamp with time zone;
  19. Акжан Абдулин
    Акжан Абдулин 5 лет назад
    хотя и так можно.