Какое-то ну очень вымученное решение, потерявшее всю легкость, непринужденность и элегантность.
...
with periods(id, start_time, stop_time) as (
values (1, '2019-03-29 07:00:00'::timestamp, '2019-04-08 14:00:00'::timestamp),
(2, '2019-04-10 07:00:00'::timestamp, '2019-04-10 20:00:00'::timestamp),
(3, '2019-04-11 12:00:00'::timestamp, '2019-04-12 16:07:12'::timestamp),
(4, '2018-12-28 12:00:00'::timestamp, '2019-01-16 16:00:00'::timestamp),
(5, '2018-06-10 18:46:10'::timestamp, '2018-06-13 10:18:18'::timestamp),
(6, '2019-04-28 21:00:00'::timestamp, '2019-04-28 21:00:00'::timestamp)
),
holidays (h_date) as (
values ('2018-01-01'),
('2018-01-02'),
('2018-01-03'),
('2018-01-04'),
('2018-01-05'),
('2018-01-07'),
('2018-01-08'),
('2018-02-23'),
('2018-03-08'),
('2018-05-01'),
('2018-05-09'),
('2018-06-12'),
('2018-11-04'),
('2018-12-31'),
('2019-01-01'),
('2019-01-02'),
('2019-01-03'),
('2019-01-04'),
('2019-01-05'),
('2019-01-07'),
('2019-01-08'),
('2019-02-23'),
('2019-03-08'),
('2019-05-01'),
('2019-05-09'),
('2019-06-12'),
('2019-11-04')
),
subst_days (s_date) as (
values ('2018-04-28'),
('2018-06-09'),
('2018-12-29')
),
magic as (
select id,
to_char(start_time, 'yyyy-mm-dd HH24:MI:SS') as start_time,
to_char(stop_time, 'yyyy-mm-dd HH24:MI:SS') as stop_time,
case
when dd = last_value(dd) over (partition by id, start_time, stop_time) and
extract(hours from stop_time) between 10 and 18
then stop_time - last_value(dd) over (partition by id, start_time, stop_time)
when extract(hours from dd) < 10
or extract(hours from dd) >= 19 then interval '0 hours'
when dd = first_value(dd) over (partition by id, start_time, stop_time) and
extract(hours from start_time) between 10 and 18
then first_value(dd) over (partition by id, start_time, stop_time) + '1 hour' - start_time
else interval '1 hour' end as w_hours
from periods,
generate_series(date_trunc('hour', start_time), date_trunc('hour', stop_time), '1 hour') dd
where (exists(select 1 from subst_days where dd::date = s_date::date) or extract(dow from dd) between 1 and 5)
and not exists(select 0
from holidays
where h_date::date = dd::date)
union all
select id,
to_char(start_time, 'yyyy-mm-dd HH24:MI:SS') as start_time,
to_char(stop_time, 'yyyy-mm-dd HH24:MI:SS') as stop_time,
interval '0 hours'
from periods,
generate_series(date_trunc('hour', start_time), date_trunc('hour', stop_time), '1 hour') dd
where not exists(select 1
from subst_days
where dd::date = s_date::date
or extract(dow from dd) between 6 and 7
or exists(select 0
from holidays
where h_date::date = dd::date)
))
select id, start_time, stop_time, to_char(sum(w_hours), 'HH24:MI:SS') as work_hrs
from magic
group by id, start_time, stop_time
order by id;
Ну и дабы уже закрыть гештальт и перестать себя чувствовать как провинившийся студент…
finally
with periods(id, start_time, stop_time) as (
values (1, '2019-03-29 07:00:00'::timestamp, '2019-04-08 14:00:00'::timestamp),
(2, '2019-04-10 07:00:00'::timestamp, '2019-04-10 20:00:00'::timestamp),
(3, '2019-04-11 12:00:00'::timestamp, '2019-04-12 16:07:12'::timestamp),
(4, '2018-12-28 12:00:00'::timestamp, '2019-01-16 16:00:00'::timestamp)
),
holidays (h_date) as (
values ('2018-01-01'),
('2018-01-02'),
('2018-01-03'),
('2018-01-04'),
('2018-01-05'),
('2018-01-07'),
('2018-01-08'),
('2018-02-23'),
('2018-03-08'),
('2018-05-01'),
('2018-05-09'),
('2018-06-12'),
('2018-11-04'),
('2018-12-31'),
('2019-01-01'),
('2019-01-02'),
('2019-01-03'),
('2019-01-04'),
('2019-01-05'),
('2019-01-07'),
('2019-01-08'),
('2019-02-23'),
('2019-03-08'),
('2019-05-01'),
('2019-05-09'),
('2019-06-12'),
('2019-11-04')
),
subst_days (s_date) as (
values ('2018-04-28'),
('2018-06-09'),
('2018-12-29')
),
magic as (
select id,
to_char(start_time, 'yyyy-mm-dd HH24:MI:SS') as start_time,
to_char(stop_time, 'yyyy-mm-dd HH24:MI:SS') as stop_time,
case
when dd = last_value(dd) over (partition by id, start_time, stop_time) and
extract(hours from stop_time) between 10 and 18
then stop_time - last_value(dd) over (partition by id, start_time, stop_time)
when dd = first_value(dd) over (partition by id, start_time, stop_time) and
extract(hours from start_time) between 10 and 18
then first_value(dd) over (partition by id, start_time, stop_time) + '1 hour' - start_time
when extract(hours from dd) < 10
or extract(hours from dd) >= 19 then interval '0 hours'
else interval '1 hour' end as w_hours
from periods,
generate_series(date_trunc('hour', start_time), date_trunc('hour', stop_time), '1 hour') dd
where (exists(select 1 from subst_days where dd::date = s_date::date) or extract(dow from dd) between 1 and 5)
and not exists(select 0
from holidays
where h_date::date = dd::date)
)
select id, start_time, stop_time, to_char(sum(w_hours), 'HH24:MI:SS') as work_hrs
from magic
group by id, start_time, stop_time
order by id;
with periods(id, start_time, stop_time) as (
values (1, '2019-03-29 07:00:00'::timestamp, '2019-04-08 14:00:00'::timestamp),
(2, '2019-04-10 07:00:00'::timestamp, '2019-04-10 20:00:00'::timestamp),
(3, '2019-04-11 12:00:00'::timestamp, '2019-04-12 16:07:12'::timestamp),
(4, '2018-12-28 12:00:00'::timestamp, '2019-01-16 16:00:00'::timestamp)
),
holidays (h_date) as (
values ('2018-01-01'),
('2018-01-02'),
('2018-01-03'),
('2018-01-04'),
('2018-01-05'),
('2018-01-07'),
('2018-01-08'),
('2018-02-23'),
('2018-03-08'),
('2018-05-01'),
('2018-05-09'),
('2018-06-12'),
('2018-11-04'),
('2019-01-01'),
('2019-01-02'),
('2019-01-03'),
('2019-01-04'),
('2019-01-05'),
('2019-01-07'),
('2019-01-08'),
('2019-02-23'),
('2019-03-08'),
('2019-05-01'),
('2019-05-09'),
('2019-06-12'),
('2019-11-04')
),
magic as (
select id,
to_char(start_time, 'yyyy-mm-dd HH24:MI:SS') as start_time,
to_char(stop_time, 'yyyy-mm-dd HH24:MI:SS') as stop_time,
dd,
first_value(dd) over (partition by id, start_time, stop_time),
last_value(dd) over (partition by id, start_time, stop_time),
case
when dd = last_value(dd) over (partition by id, start_time, stop_time) and
extract(hours from stop_time) between 10 and 18
then stop_time - last_value(dd) over (partition by id, start_time, stop_time)
when dd = first_value(dd) over (partition by id, start_time, stop_time) and
extract(hours from start_time) between 10 and 18
then first_value(dd) over (partition by id, start_time, stop_time) + '1 hour' - start_time
else interval '1 hour' end as w_hours
from periods,
generate_series(date_trunc('hour', start_time), date_trunc('hour', stop_time), '1 hour') dd
where extract(dow from dd) between 1 and 5
and extract(hours from dd) >= 10
and extract(hours from dd) < 19
and not exists(select 0 from holidays where h_date::date = dd::date)
)
select id, start_time, stop_time, to_char(sum(w_hours), 'HH24:MI:SS') as work_hrs
from magic
group by id, start_time, stop_time
order by id;
with periods(id, start_time, stop_time) as (
values (1, '2019-03-29 07:00:00'::timestamp, '2019-04-08 14:00:00'::timestamp),
(2, '2019-04-10 07:00:00'::timestamp, '2019-04-10 20:00:00'::timestamp),
(3, '2019-04-11 12:00:00'::timestamp, '2019-04-12 16:07:12'::timestamp),
(4, '2018-12-28 12:00:00'::timestamp, '2019-01-16 16:00:00'::timestamp)
),
holidays (h_date) as (
values ('2018-01-01'),
('2018-01-02'),
('2018-01-03'),
('2018-01-04'),
('2018-01-05'),
('2018-01-07'),
('2018-01-08'),
('2018-02-23'),
('2018-03-08'),
('2018-05-01'),
('2018-05-09'),
('2018-06-12'),
('2018-11-04'),
('2019-01-01'),
('2019-01-02'),
('2019-01-03'),
('2019-01-04'),
('2019-01-05'),
('2019-01-07'),
('2019-01-08'),
('2019-02-23'),
('2019-03-08'),
('2019-05-01'),
('2019-05-09'),
('2019-06-12'),
('2019-11-04')
),
magic as (
select id,
to_char(start_time, 'yyyy-mm-dd HH24:MI:SS') as start_time,
to_char(stop_time, 'yyyy-mm-dd HH24:MI:SS') as stop_time,
dd,
case
when dd = last_value(dd) over (partition by id, start_time, stop_time) and
extract(hours from stop_time) between 10 and 18
then stop_time - last_value(dd) over (partition by id, start_time, stop_time)
else interval '1 hour' end as w_hours
from periods,
generate_series(start_time, stop_time, '1 hour') dd
where extract(dow from dd) between 1 and 5
and extract(hours from dd) >= 10
and extract(hours from dd) < 19
and not exists(select 0 from holidays where h_date::date = dd::date)
)
select id, start_time, stop_time, to_char(sum(w_hours), 'HH24:MI:SS') as work_hrs
from magic
group by id, start_time, stop_time
order by id;
with periods(id, start_time, stop_time) as (
values (1, '2019-03-29 07:00:00'::timestamp, '2019-04-08 14:00:00'::timestamp),
(2, '2019-04-10 07:00:00'::timestamp, '2019-04-10 20:00:00'::timestamp),
(3, '2019-04-11 12:00:00'::timestamp, '2019-04-12 16:07:12'::timestamp),
(4, '2018-12-28 12:00:00'::timestamp, '2019-01-16 16:00:00'::timestamp)
),
holidays (h_date) as (
values ('2018-01-01'),
('2018-01-02'),
('2018-01-03'),
('2018-01-04'),
('2018-01-05'),
('2018-01-07'),
('2018-01-08'),
('2018-02-23'),
('2018-03-08'),
('2018-05-01'),
('2018-05-09'),
('2018-06-12'),
('2018-11-04'),
('2019-01-01'),
('2019-01-02'),
('2019-01-03'),
('2019-01-04'),
('2019-01-05'),
('2019-01-07'),
('2019-01-08'),
('2019-02-23'),
('2019-03-08'),
('2019-05-01'),
('2019-05-09'),
('2019-06-12'),
('2019-11-04')
),
magic as (
select id,
start_time,
stop_time,
dd,
case
when dd = last_value(dd) over (partition by id, start_time, stop_time) and
extract(hours from stop_time) between 10 and 19
then stop_time - last_value(dd) over (partition by id, start_time, stop_time)
else interval '1 hour' end as w_hours
from periods,
generate_series(start_time, stop_time, '1 hour') dd
where extract(dow from dd) between 1 and 5
and extract(hours from dd) >= 10
and extract(hours from dd) < 19
and not exists(select 0 from holidays where h_date::date = dd::date)
)
select id, start_time, stop_time, to_char(sum(w_hours), 'HH24:MI:SS') as work_hrs
from magic
group by id, start_time, stop_time
order by id;
Третий пгадмин вис хотя бы в процессе эксплуатации, ругался и вылетал. Четвертый пошел дальше, если просто оставить его свернутым, а потом развернуть — 7 секунд!!! Я целых 7 секунд могу считать пылинки на белом фоне моего монитора. Это core i7 и 8 гиг RAM.
К слову, тот же DBeaver просто шедевр на фоне пгадминов. Хоть и со своими тараканами.
И главная печаль… Всему опэнсорсу не хватает дебага сиквельного кода из коробки((
О. это извечная проблема. Особенно при переводе статей. Назовешь по-русски — теряется смысл, по-английски — выбивается из текста. Получается замкнутый круг из мучений и компромиссов с самим собой.
1. Проблема в поиске именно двух подряд идущих слов. Например, Иван Иванов. Из очевидного — искали регуляркой по тексту уже полученных через поиск по вектору данных. Гибкости абсолютно никакой. Необходимо учитывать все вариации окончаний обоих слов, плюс одно дело это формировать руками в базе, и совсем другое — в автоматическом режиме. Да еще и запрос притормаживает ощутимо, так как лайк, или симилар.
2. Использовали GIN.
Там вообще все очень непросто, увы. Но спасибо за совет.
Изменения там регулярные, кроме того, долгие хождения по вектору, тяжелому вектору. С вакуумом и статистикой особых проблем нет. Все регулярно, по мере необходимости. Помимо автовакуума, само собой.
К слову, тот же DBeaver просто шедевр на фоне пгадминов. Хоть и со своими тараканами.
И главная печаль… Всему опэнсорсу не хватает дебага сиквельного кода из коробки((
2. Использовали GIN.
Было:
Стало:
И сам explain:
О — оптимизация)