sqlpostgresqljsonb-array-elements

Exclude from the selection the values ​that are in the array


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


Solution

  • If I understand your problem well, there are two things we can do:

    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: