Сначала необходимо рассчитать продолжительность для каждой комбинации входа / выхода.
Предполагая, что у вас всегда есть четное количество записей для каждой даты для каждого staff_id, вы можете рассчитать продолжительность для каждой пары следующим образом:
select personnel_id,
"date",
case
when row_number() over w % 2 = 0 then "time" - lag("time") over w
end as duration
from person_work
window w as (partition by personnel_id, "date" order by "time")
row_number()
- это оконная функция , которая присваивает номер каждой строке. lag()
- это еще одна оконная функция, которая получает значение столбца из предыдущей строки. Поскольку обе функции имеют одно и то же «определение окна», я объявил об этом только один раз с предложением window
в конце. Выражение CASE
вычисляет разницу столбца time
для каждой второй строки. Строки входа имеют нечетный номер строки, строки выхода имеют четный номер строки. % 2
проверяет четность номеров строк.
На следующем шаге нам нужно объединить пары в длительности за месяц.
Это можно сделать, опираясь на предыдущий запрос. Я использую общее табличное выражение для повторного использования предыдущего запроса:
with hours as (
select personnel_id,
"date",
case
when row_number() over w % 2 = 0 then
-- this converts the interval into a decimal value
extract(epoch from "time" - lag("time") over w)/3600
end as hours
from person_work
window w as (partition by personnel_id, "date" order by "time")
), hours_per_month as (
select personnel_id,
extract(year from "date")::int as work_year,
extract(month from "date")::int as work_month,
sum(hours) work_hours
from hours
where hours is not null
group by personnel_id, work_year, work_month
)
select *
from hours_per_month;
extract(year from ...)
возвращает год столбца date
в виде десятичного значения. ::int
- это тип приведенный , который просто преобразует его в целое число. Строго говоря, в этом нет необходимости.
extract(epoch from ..)
возвращает длительность интервала в секундах. Разделив этот результат на 3600, вы получите интервал в часах.
Это вернет что-то вроде:
personnel_id | work_year | work_month | work_hours
-------------+-----------+------------+-----------
1 | 2018 | 1 | 25.33
1 | 2018 | 2 | 17.08
1 | 2018 | 3 | 8.25
Затем на последнем шаге нам нужно превратить строки в столбцы. Это можно сделать с помощью условного агрегирования, используя предложение filter :
with hours as (
select personnel_id,
"date",
case
when row_number() over w % 2 = 0 then extract(epoch from "time" - lag("time") over w)/3600
end as hours
from person_work
window w as (partition by personnel_id, "date" order by "time")
), hours_per_month as (
select personnel_id,
extract(year from "date")::int as work_year,
extract(month from "date")::int as work_month,
sum(hours) hours
from hours
where hours is not null
group by personnel_id, work_year, work_month
)
select personnel_id,
work_year,
sum(hours) filter (where work_month = 1) as hours_jan,
sum(hours) filter (where work_month = 2) as hours_feb,
sum(hours) filter (where work_month = 3) as hours_mar,
sum(hours) filter (where work_month = 4) as hours_apr,
sum(hours) filter (where work_month = 5) as hours_may,
sum(hours) filter (where work_month = 6) as hours_jun,
sum(hours) filter (where work_month = 7) as hours_Jul,
sum(hours) filter (where work_month = 8) as hours_aug,
sum(hours) filter (where work_month = 9) as hours_sep,
sum(hours) filter (where work_month = 10) as hours_oct,
sum(hours) filter (where work_month = 11) as hours_nov,
sum(hours) filter (where work_month = 12) as hours_dec
from hours_per_month
group by personnel_id, work_year;
Это возвращает что-то вроде этого:
personnel_id | work_year | hours_jan | hours_feb | hours_mar | hours_apr | hours_may | hours_jun | hours_jul | hours_aug | hours_sep | hours_oct | hours_nov | hours_dec
-------------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+----------
1 | 2018 | 25.33 | 17.08 | 8.25 | ... | ... | ... | ... | ... | .... | .... | ... | ....
Если вы просто хотите получить отчет за один год, вы можете использовать where work_year = ...
в окончательном выборе и удалить столбец из списка выбора и group by
Онлайн пример: https://rextester.com/OEEAZ64654