Search code examples
sqltimeoracle-sqldeveloperboolean-logicgranularity

how do i convert the working hours to Boolean?


how do I convert the working hours 08:00:00-11:59:00;13:00:00-16:59:00; into 48 digit Boolean format like

 "000000000000000011111111001111111100000000000000" 

where each digit refers to 30 min granularity using Oracle SQL Query?


Solution

  • Assuming you are starting from a string which always has semicolon-separated pairs of from/to times, with a trailing semicolon after the last pair; and those times are always HH24:MI:SS with the seconds always zero as shown; then... you can split the string into multiple string pairs representing each from/to pair:

    select regexp_substr('08:00:00-11:59:00;13:00:00-16:59:00;', '(.*?)(;|-|$)', 1, (2 * level - 1), null, 1),
      regexp_substr('08:00:00-11:59:00;13:00:00-16:59:00;', '(.*?)(;|-|$)', 1, 2 * level, null, 1)
    from dual
    connect by level <= regexp_count('08:00:00-11:59:00;13:00:00-16:59:00;', ';')
    
    REGEXP_S REGEXP_S
    -------- --------
    08:00:00 11:59:00
    13:00:00 16:59:00
    

    And you can generate all the half-hour blocks in a nominal day (picking one not subject to a DST switch):

    select to_char(date '2000-01-01' + ((level - 1) / 48), 'HH24:MI":00"')
    from dual
    connect by level <= 48
    
    TO_CHAR(
    --------
    00:00:00
    00:30:00
    01:00:00
    01:30:00
    02:00:00
    ...
    23:00:00
    23:30:00
    

    And then join those together to see where there is an overlap, using string comparison (which is why the time format matters); using CTEs to provide the initial string for simplicity and then the results of the two previous queries:

    with t1 (working_hours) as (
      select '08:00:00-11:59:00;13:00:00-16:59:00;' from dual
    ),
    t2 (working_from, working_to) as (
      select regexp_substr(working_hours, '(.*?)(;|-|$)', 1, (2 * level - 1), null, 1),
        regexp_substr(working_hours, '(.*?)(;|-|$)', 1, 2 * level, null, 1)
      from t1
      connect by level <= regexp_count(working_hours, ';')
    ),
    t3 (block_from) as (
      select to_char(date '2000-01-01' + ((level - 1) / 48), 'HH24:MI":00"')
      from dual
      connect by level <= 48
    )
    select block_from,
      case when t2.working_from is null then 0 else 1 end as flag
    from t3
    left join t2 on t2.working_from <= t3.block_from and t2.working_to >= t3.block_from
    
    BLOCK_FROM  FLAG
    ----------  ----
    00:00:00    0
    00:30:00    0
    ...
    07:30:00    0
    08:00:00    1
    08:30:00    1
    ...
    11:00:00    1
    11:30:00    1
    12:00:00    0
    12:30:00    0
    13:00:00    1
    13:30:00    1
    ...
    16:00:00    1
    16:30:00    1
    17:00:00    0
    ...
    23:00:00    0
    23:30:00    0
    

    And then finally aggregate those together into a single result string:

    with t1 (working_hours) as (
      select '08:00:00-11:59:00;13:00:00-16:59:00;' from dual
    ),
    t2 (working_from, working_to) as (
      select regexp_substr(working_hours, '(.*?)(;|-|$)', 1, (2 * level - 1), null, 1),
        regexp_substr(working_hours, '(.*?)(;|-|$)', 1, 2 * level, null, 1)
      from t1
      connect by level <= regexp_count(working_hours, ';')
    ),
    t3 (block_from) as (
      select to_char(date '2000-01-01' + ((level - 1) / 48), 'HH24:MI":00"')
      from dual
      connect by level <= 48
    )
    select listagg(case when t2.working_from is null then 0 else 1 end)
      within group (order by t3.block_from) as result
    from t3
    left join t2 on t2.working_from <= t3.block_from and t2.working_to >= t3.block_from
    
    RESULT
    ------------------------------------------------
    000000000000000011111111001111111100000000000000
    

    db<>fiddle

    If your initial string is actually coming from a table and you need this conversion for multiple rows at once then the connect-by split is a bit more complicated, and a recursive CTE might be more suitable for that part.

    Just for fun, here's an example.