#sql #postgresql #datetime #gaps-and-islands #date-arithmetic
#sql #postgresql #дата и время #пробелы и острова #дата-арифметика
Вопрос:
Я пытаюсь рассчитать ежемесячное рабочее время каждого курьера. Это данные компании-курьера, и у каждого курьера разное время, когда он начинает свою смену и заканчивает ее.
Это пример того, как выглядит таблица:
Type_ID | Delivery_Guy_ID | Date_Created |
---|---|---|
5000 | 210 | 2020-11-16 16:34:43 |
7000 | 210 | 2020-11-16 16:35:24 |
3000 | 210 | 2020-11-16 16:35:46 |
3000 | 210 | 2020-11-16 16:37:41 |
4000 | 210 | 2020-11-16 16:39:41 |
3000 | 210 | 2020-11-16 16:42:53 |
4000 | 210 | 2020-11-16 16:47:53 |
3000 | 210 | 2020-11-16 16:48:16 |
4000 | 210 | 2020-11-16 16:50:16 |
3000 | 210 | 2020-11-16 16:53:01 |
2000 | 210 | 2020-11-16 18:53:07 |
Идентификатор типа = 2000 означает «Окончание смены», а идентификатор типа = 7000 означает «Начало смены». Курьер может начинать свою смену и заканчивать ее несколько раз в течение дня.
Это запрос, который я пробовал:
WITH working_hours_cte AS
(
SELECT *
FROM (
SELECT id AS id
,
type_id AS type_id
,
delivery_guy_id AS delivery_guy_id
,
Timezone('Africa/Cairo', date_created) as date_created
FROM delivery_guys_event
ORDER BY delivery_guy_id,
date(date_created),
date_created ) AS t
WHERE t.date_created >= date('11-01-2020')
AND t.date_created <= date('11-30-2020') ), get_shift_start_time_cte AS
(
SELECT DISTINCT
ON(
t.delivery_guy_id) t.* ,
t.date_created::timestamptz AS shift_start_time
FROM working_hours_cte AS t
WHERE t.type_id = 7000
GROUP BY 1,
2,
3,
4
ORDER BY t.delivery_guy_id,
date(date_created),
t.date_created), get_shift_end_time_cte AS
(
SELECT DISTINCT
ON(
t.delivery_guy_id) t.* ,
t.date_created::timestamptz AS shift_end_time
FROM working_hours_cte AS t
WHERE t.type_id = 2000
GROUP BY 1,
2,
3,
4
ORDER BY t.delivery_guy_id,
date(date_created),
t.date_created), get_working_hours_cte1 AS
(
SELECT base.* ,
(date_part('day', ap.shift_end_time::timestamptz - aa.shift_start_time::timestamptz) * 24 date_part('hour', ap.shift_end_time::timestamptz - aa.shift_start_time::timestamptz)) * 60 date_part('minute', ap.shift_end_time::timestamptz - aa.shift_start_time::timestamptz) AS working_hours
FROM working_hours_cte AS base
LEFT JOIN get_shift_start_time_cte AS aa
ON base.delivery_guy_id = aa.delivery_guy_id
LEFT JOIN get_shift_end_time_cte AS ap
ON base.delivery_guy_id = ap.delivery_guy_id)
SELECT u.NAME AS delivery_guy_name
,
round(sum(timings.working_hours::numeric),0)/60 AS working_hours
FROM get_working_hours_cte1 AS timings
LEFT JOIN delivery_guys_contacts AS u
ON timings.delivery_guy_id = u.id
WHERE timings.date_created >= date('11/01/2020')
AND timings.date_created <= date('11/30/2020')
GROUP BY delivery_guy_name
ORDER BY delivery_guy_name ASC
Желаемый результат:
Delivery_guy_name | Working_hours |
---|---|
Джон | 200 |
Майкл | 150 |
Джо | 230 |
Я также пытался самостоятельно присоединиться, но, похоже, ни один из них не дает правильного количества часов. Может кто-нибудь, пожалуйста, скажите мне, в чем проблема?
Комментарии:
1. пожалуйста, также укажите желаемый результат
2. Я только что отредактировал вопрос
Ответ №1:
Как насчет суммирования разницы во времени между текущей строкой и следующей строкой, за исключением случаев, когда текущая строка является концом смены? Это можно легко сделать с помощью оконных функций:
select delivery_guy_id, sum(diff) as total_diff
from (
select delivery_guy_id,
lead(date_created) over(partition by delivery_guy_id order by date_created)
- date_created as diff
from mytable
) t
wxhere type_id <> 2000
group by delivery_guy_id
Это будет работать, если нет записей за пределами начала / конца отправки, то есть за окончанием смены всегда следует начало смены.
Комментарии:
1. Большое вам спасибо за предупреждение! Я упорядочил данные и включил только конечные смены и первые смены, причем за конечными сменами всегда следуют начальные смены. Я не мог разделить по delivery_guy_id, потому что я хотел посмотреть на каждую смену независимо. Другими словами, вычтите строку 2 из строки 1 и строку 4 из строки 3. Это сработало, когда я использовал функцию ntile() и присвоил один и тот же номер строки каждые двум строкам. Затем я разделил раздел по номерам строк и сумме над разделом () по delivery_guy_id . Так что спасибо, что наставили меня на правильный путь!