I have two tables:
my_temp:
| employee_id | date | start_granula | end_granula | count_seconds |
|---|---|---|---|---|
| 2223eb0f0d0x | 2025-08-22 | 07:40:00 | 07:50:00 | 625 |
| 2223eb0f0d0x | 2025-08-22 | 08:10:00 | 08:20:00 | 513 |
| 2223eb0f0d0x | 2025-08-22 | 12:35:00 | 12:41:00 | 128 |
| 2223eb0f0d0x | 2025-08-24 | 15:10:00 | 15:25:00 | 3206 |
schedules:
| employee_id | start_time | end_time | breaks | workdays |
|---|---|---|---|---|
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 08:00:00 | 17:00:00 | [{"to": "13:00", "from": "12:30", "nextDay": false}, {"to": "15:00", "from": "15:30", "nextDay": false}] |
[1, 2, 3, 4, 5] |
I need to select only those time ranges from my_temp that correspond to the employee's schedule. Breaks must be excluded.
It is also necessary to check whether this day was a working day for the employee according to the schedule. If it is not, then leave only one entry in the table for this date and in column day_type write one of the following values: ['working_day', 'not_working_day']. In the start_time and end_time columns, write the time according to the work schedule.
I'm having trouble excluding all entries that fall during the employee's break, entries that go beyond working hours are not excluded.
How to leave only one entry in the query for a date that is not a working day for the employee?
My query:
select
e.employee_id,
e.date,
case when
EXTRACT(DOW FROM e.date) = ANY(SELECT jsonb_array_elements(s.workDays::jsonb))
then granula_start else s.start_time end as granula_start,
case when
EXTRACT(DOW FROM e.date) = ANY(SELECT jsonb_array_elements(s.workDays::jsonb))
then granula_end else s.end_time end as granula_end,
count_seconds,
case when
EXTRACT(DOW FROM e.date) = ANY(SELECT jsonb_array_elements(s.workDays::jsonb))
then 'working_day' else 'not_working_day' end as day_type
FROM my_temp e
inner join schedules s on s.employee_id = e.employee_id
where
granula_start
>=
s.start_time
and not exists (
select 1 from jsonb_array_elements(s.breaks::jsonb) br
where granula_start > (br->>'from')::time
and granula_end < (br->>'to')::time
)
which returns:
| employee_id | date | granula_start | granula_end | count_seconds | day_type |
|---|---|---|---|---|---|
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-22 | 08:10:00 | 08:20:00 | 513 | working_day |
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-22 | 13:10:00 | 13:25:00 | 1205 | working_day |
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-22 | 15:10:00 | 15:25:00 | 3206 | working_day |
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-22 | 17:10:00 | 17:25:00 | 1205 | working_day |
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-24 | 08:00:00 | 17:00:00 | 6504 | not_working_day |
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-24 | 08:00:00 | 17:00:00 | 7203 | not_working_day |
Please help me. Here is my example: https://dbfiddle.uk/G542L9Wh
If I understand your problem well, there are two things we can do:
case when extract(…) = any(…) then x else y end 3 times, we will put it as a new column wd ("Working Day") in a Common Table Expression that we will later reuse (hence the name "Common"!)@> to test for a JSONB array being a subset of another one. So by transforming our EXTRACT(…) to a JSONB array of 1 element, we can directly test if it is a subset of workDays:s.workDays @> to_jsonb(array[EXTRACT(DOW FROM e.date)])group by case when <working day> then <criteria to group by granule> else <criteria to group by whole day> end: as granules are supposed unique, this will get 1 row per granule on working days, but 1 row for the whole day for non-working days (as intended)with wd as
(
select
e.employee_id,
e.date,
e.count_seconds,
e.granula_start,
e.granula_end,
s.start_time,
s.end_time,
s.workDays @> to_jsonb(array[EXTRACT(DOW FROM e.date)]) wd
FROM my_temp e
inner join schedules s on s.employee_id = e.employee_id
where
granula_start
>=
s.start_time
and not exists (
select 1 from jsonb_array_elements(s.breaks::jsonb) br
where granula_start >= (br->>'from')::time
and granula_end <= (br->>'to')::time
)
)
select
employee_id, date,
case when wd then granula_start else start_time end granula_start,
case when wd then granula_end else end_time end granula_end,
sum(count_seconds) count_seconds,
wd
from wd
group by 1, 2, 3, 4, 6 -- Group by employee_id, date, and the two formulas to choose either the granula-specific start and end, or the start and end of the day for non-working days (don't bother repeating them here, use PostgreSQL's ability to specify "the 3rd and 4th columns"), as well as the "working day" flag.
order by 1, 2, 3;
This is shown as the last query of a fiddle, returning from your data:
| employee_id | date | granula_start | granula_end | count_seconds | wd | (comment) |
|---|---|---|---|---|---|---|
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-22 | 08:10:00 | 08:20:00 | 513 | t | |
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-22 | 13:10:00 | 13:25:00 | 1205 | t | |
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-22 | 15:10:00 | 15:25:00 | 3206 | t | |
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-22 | 17:10:00 | 17:25:00 | 1205 | t | "entries that go beyond working hours are not excluded" |
| 3dd280f2-e4d3-4568-9d97-1cc3a9dff1e9 | 2025-08-24 | 08:00:00 | 17:00:00 | 13707 | f | 6504 + 7203, aggregated as we're on a non-working day |
Due to the question having some points vague, there are limitations to this solution:
> and < in the "break rule" to >= and <=: if a granule starts at exactly 12:30 (= start of the break), it will now be excluded (part of the break), while your query counted it as included in working hours.where granula_start >= s.start_time seems contradictory to your rule "entries that go beyond working hours are not excluded". Sticking to the SQL I'll interpret it as "entries out of schedule are excluded if they are before schedule, not exluded if they are after the schedule