Как стать автором
Обновить

Комментарии 23

НЛО прилетело и опубликовало эту надпись здесь
Спасибо за поддержку. Выбирать в самом деле было сложно.
Своё решение писал, исходя из предпосылки — что, если у нас будут периоды длительностью в тысячи лет (ну или хотя бы в сотни)? :)
НЛО прилетело и опубликовало эту надпись здесь
Круто, почаще бы такие штуки видеть.
Я работал с постгресом на заре свой карьеры, был тогда полным чайником, а потом жизнь заставила перейти на оракл. Теперь потихонечку начинаю использовать постгрес для своих проектов, очень познавательно и полезно получается — сразу кучу всего увидел.
Пожалуй что именно его решение (ссылка) показалось мне самым интересным по компактности и выразительности.

Но не по производительности.
Его решение работает в 10 раз медленнее, чем, например, мое.

Без претензий на лавры, PostgreSQL вообще не мой родной.
НЛО прилетело и опубликовало эту надпись здесь
Интереса ради померял оба ваших решения на скорость. Да, ваши запросы действительно на (десятичный) порядок быстрее. 10% не уловил, более-менее равная производительность на количестве периодов около 3 тыс. Ну может чуть быстрее, но это уже сравнимо с погрешностями измерений.

Но пожалуй производительность тут не была основным критерием, если только запрос не тормозил слишком уж чрезмерно. Интереса ради я написал запрос, который работает быстрее ваших ещё на (десятичный) порядок, избавившись от суммы по интервалам, и получив скорость работы линейно зависящую только от количества периодов. Видимо придётся добавить в разбор задачи слова про производительность.
Интереса ради я написал запрос, который работает быстрее ваших ещё на (десятичный) порядок, избавившись от суммы по интервалам, и получив скорость работы линейно зависящую только от количества интервалов. Видимо придётся добавить в разбор задачи слова про производительность.

Вот на это интересно посмотреть, буду ждать.
Не ждите слишком многого. Разбор решения будет интересен скорее тем, кто не представляет себе, как такие задачи вообще решаются. Для Вас, соответственно, это будет слишком тривиально.
Видимо придётся добавить в разбор задачи слова про производительность.
Конечно, на вкус и цвет все фломастеры разные, но на мое имхо, производительность в БД — это самое интересное.
Интереса ради я написал запрос, который работает быстрее ваших ещё на (десятичный) порядок, избавившись от суммы по интервалам, и получив скорость работы линейно зависящую только от количества интервалов. Видимо придётся добавить в разбор задачи слова про производительность.

Так моё ж решение так и работает, или я что-то не понимаю?
Да, Вы пошли именно в этом направлении, но немного не дошли до конца. Можно посчитать один раз количество рабочих часов на каждый день в календаре и дальше уже брать готовый ответ, не считая каждый раз количества рабочих и выходных дней, попадающих в период.
Вот так, например:
with recursive calendar(d, is_working) as (
      select '2017-12-31'::date, 0
      union all
      select d+1
           , case when extract(dow from d+1) not in (0,6)
                   and d+1 <> all('{2019-01-01,2019-01-02,2019-01-03,2019-01-04,2019-01-07,2019-01-08,2019-03-08,2019-05-01,2019-05-02,2019-05-03,2019-05-09,2019-05-10,2019-06-12,2019-11-04,2018-01-01,2018-01-02,2018-01-03,2018-01-04,2018-01-05,2018-01-08,2018-02-23,2018-03-08,2018-03-09,2018-04-30,2018-05-01,2018-05-02,2018-05-09,2018-06-11,2018-06-12,2018-11-05,2018-12-31}')
                    or d+1 = any('{2018-04-28,2018-06-09,2018-12-29}')
                  then 1 else 0 end
         from calendar where d < '2020-01-01'
), calendar_w(d, is_working, work_hours_acc) as (
  select d, is_working
       , sum(is_working*'9 hours'::interval) over (order by d range between unbounded preceding and current row)
    from calendar
)
select p.*
     , c2.work_hours_acc - c1.work_hours_acc
       + ('19:00:00'::time - least(greatest(p.start_time::time,'10:00:00'::time),'19:00:00'::time)) * c1.is_working
       - ('19:00:00'::time - least(greatest(p.stop_time::time, '10:00:00'::time),'19:00:00'::time)) * c2.is_working as work_hours
  from periods p, calendar_w c1, calendar_w c2
 where c1.d = p.start_time::date
   and c2.d = p.stop_time::date

Да, Вы пошли именно в этом направлении, но немного не дошли до конца


хм. В моём решении смысл был другой — избавиться от перехода от данных {одна строка на период} к данным {одна строка на дату} и от последующей агрегации. В решении приведенном сверху вы красиво избавились от агрегации и от увеличения количества данных на каждую строку, но все равно осталась необходимость увеличения количества данных один раз.
В моём решении сложности добавляет то, что необходимо проверять попадание праздников в период. То есть у меня сложность была, грубо говоря (если для простоты считать, что джойн выполняется за константу независимую от объема данных), O(количество праздников * количество периодов), а у Вас O(количество дней между минимальной и максимальной допустимой датой + 2 * количество периодов). То есть, скорее всего, с небольшим количеством праздников или с периодами длиной в сто лет, думаю, моё решение будет работать быстрее, но с большим количеством периодов в интервале двух годов и/или большим количеством праздников — будет быстрее работать Ваше.
Я посчитал сразу количество рабочих часов на каждую дату и расчёт длительности для периодов в рабочих часах стал одной операцией вычитания. А у Вас в результате остались итерации по каждому из периодов. Посмотрите на планы исполнения мой и Ваш. Именно по этой причине я и сказал, что Вы не дошли до конца. В философском смысле. Уж если от внутреннего цикла избавляться, то полностью, чего уж там.

И, кстати, именно Ваше решение сподвигло меня на эти размышления и в конечном итоге реализацию такого подхода, как самого оптимального по времени.
А ваше решение работает на 10% медленнее чем моё и дело там не в постгресе:)

Тут можно сравнить более объективно:
Ваше решение
Мое

И у Вас ошибка где-то, не сходится сумма…
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Я лишь взял Ваше решение по вашей же ссылке. Почему у вас там оказался неверный набор выходных и праздников — спросите у себя.
Так что тут неизвестно кто первый не озаботился. :)

Ваше решение мне нравится лаконичностью, понятностью и чистотой.

Решение от OrmEugensson более мудреное и сложное, но и гораздо более производительное, чем наши с вами.

С чего вы взяли что ваша задача (подсчет общей суммы массива), более актуальнее моей (подсчет для конкретной заявки)?

Мне такие кейсы встречаются чаще. Как-то даже не пришло в голову иначе протестировать.
НЛО прилетело и опубликовало эту надпись здесь
Не занимайтесь демагогией. Ваше решение даёт неправильный результат, потому что Вы не все даты из рабочего календаря включили в свой запрос. И чтобы получить правильный результат, надо сперва вдумчиво Ваш запрос дописать. Я на это указывал ещё в комментариях к предыдущей статье. Мне во время проверки приходилось делать исправления (что, признаться, несколько раздражало), так как я вроде бы был заводилой всей этой движухи, а прочим окружающим этим заниматься совершенно неинтересно.
НЛО прилетело и опубликовало эту надпись здесь
Мда… Это не бубль-гум нифига не iso.
Впрочем, меня обнадеживает, что я практически все понял, всего то раза три загуглив :-)
… можно тешить себя надеждой, что когда приспичит — смогу пересесть.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий