Search code examples
oracle-database

How to generate Zero values in a gap


I have some data where there is simply a gap in the data and I need those gaps to be value '0'. Given the data below, thing 'a', 'b' and 'c' would ideally have +/- 12 hours of data around a given period. Where no data exists, that should be a 0

select *
from (
    select 'a' as thing, timestamp '2024-12-19 11:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 12:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 13:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 14:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 15:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 16:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 17:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 18:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 19:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 20:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 21:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 22:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'a' as thing, timestamp '2024-12-19 23:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'b' as thing, timestamp '2024-12-19 15:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'b' as thing, timestamp '2024-12-19 16:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'b' as thing, timestamp '2024-12-19 17:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'b' as thing, timestamp '2024-12-19 18:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'b' as thing, timestamp '2024-12-19 19:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'b' as thing, timestamp '2024-12-19 20:00:00' as t, DBMS_RANDOM.value() as v from dual union all
    select 'c' as thing, timestamp '2024-12-19 12:00:00' as t, DBMS_RANDOM.value() as v from dual
) where t between  timestamp '2024-12-19 20:00:00' - interval '12' hour
    and  timestamp '2024-12-19 11:00:00' + interval  '12' hour

I know this is possible, but I am not quite sure how.


Solution

  • This can be solved by generating all of the combinations of things and hours that you are interested in and then LEFT OUTER JOINING that generated data to your actual table.

    In your example it would look like:

    WITH input_data AS 
    (
      select *
      from (
        select 'a' as thing, timestamp '2024-12-19 11:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 12:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 13:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 14:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 15:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 16:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 17:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 18:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 19:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 20:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 21:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 22:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'a' as thing, timestamp '2024-12-19 23:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'b' as thing, timestamp '2024-12-19 15:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'b' as thing, timestamp '2024-12-19 16:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'b' as thing, timestamp '2024-12-19 17:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'b' as thing, timestamp '2024-12-19 18:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'b' as thing, timestamp '2024-12-19 19:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'b' as thing, timestamp '2024-12-19 20:00:00' as t, DBMS_RANDOM.value() as v from dual union all
        select 'c' as thing, timestamp '2024-12-19 12:00:00' as t, DBMS_RANDOM.value() as v from dual
      ) 
    )
    ,hours AS 
    (
      select timestamp '2024-12-19 11:00:00' + numtodsinterval(rownum,'HOUR') as hr
      from dual
      connect by level <= 12
    )
    ,things AS
    (
      SELECT DISTINCT thing FROM input_data
    )
    /*cross join to generate all the data you are wanting*/
    , hourthings AS
    (
      SELECT hr, thing
      FROM hours 
         CROSS JOIN things
    )
    /*now join it all together*/
    SELECT 
      hourthings.thing,
      hourthings.hr,
      COALESCE(input_data.v, 0) as v
    FROM hourthings
      LEFT OUTER JOIN input_data
         ON hourthings.thing = input_data.thing
         AND hourthings.hr = input_data.t
    ORDER BY thing, hr, v;
    
    
    +-------+------------------------------+-----------------------------------------+
    | THING |              HR              |                    V                    |
    +-------+------------------------------+-----------------------------------------+
    | a     | 19-DEC-24 12.00.00.000000000 | .69774073052016968823672302782349609322 |
    | a     | 19-DEC-24 13.00.00.000000000 | .74681895242556208805921962750792112372 |
    | a     | 19-DEC-24 14.00.00.000000000 | .13895201336452094187697669624078005226 |
    | a     | 19-DEC-24 15.00.00.000000000 | .45337500667932949332016060056416492918 |
    | a     | 19-DEC-24 16.00.00.000000000 | .18899747383037346346775198378542334477 |
    | a     | 19-DEC-24 17.00.00.000000000 | .67102219547677447329531648462053776372 |
    | a     | 19-DEC-24 18.00.00.000000000 | .66857093410415223366767066184514813993 |
    | a     | 19-DEC-24 19.00.00.000000000 | .40391951442247044543080670749491385357 |
    | a     | 19-DEC-24 20.00.00.000000000 | .66678394623467745293923962502423558974 |
    | a     | 19-DEC-24 21.00.00.000000000 | .08192799627659883827769961887128183705 |
    | a     | 19-DEC-24 22.00.00.000000000 | .82864536740652777939535875967936388967 |
    | a     | 19-DEC-24 23.00.00.000000000 | .06074762180454381335128190562038752096 |
    | b     | 19-DEC-24 12.00.00.000000000 |                                       0 |
    | b     | 19-DEC-24 13.00.00.000000000 |                                       0 |
    | b     | 19-DEC-24 14.00.00.000000000 |                                       0 |
    | b     | 19-DEC-24 15.00.00.000000000 | .49068229240906850444551902753208786223 |
    | b     | 19-DEC-24 16.00.00.000000000 | .49309406570981735526120776844366484152 |
    | b     | 19-DEC-24 17.00.00.000000000 | .81504764682984037766335094773881648938 |
    | b     | 19-DEC-24 18.00.00.000000000 | .56003821415269263216558677201167824597 |
    | b     | 19-DEC-24 19.00.00.000000000 | .47232889342064636947176597827340419611 |
    | b     | 19-DEC-24 20.00.00.000000000 | .26243408497186222403849841769849597216 |
    | b     | 19-DEC-24 21.00.00.000000000 |                                       0 |
    | b     | 19-DEC-24 22.00.00.000000000 |                                       0 |
    | b     | 19-DEC-24 23.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 12.00.00.000000000 | .82675839474283690631176461696378570193 |
    | c     | 19-DEC-24 13.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 14.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 15.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 16.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 17.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 18.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 19.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 20.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 21.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 22.00.00.000000000 |                                       0 |
    | c     | 19-DEC-24 23.00.00.000000000 |                                       0 |
    +-------+------------------------------+-----------------------------------------+
    

    A working example of this can be found at this dbfiddle

    That SQL is more verbose than necessary as the three last CTEs could be collapsed into a single statement, but this more verbose write-up will help for understanding each logical step.