SMORF
SMORF

Reputation: 499

ORACLE SQL: Fill in missing dates

I have the following code which gives me production dates and production volumes for a thirty day period.

select 
(case when trunc(so.revised_due_date) <= trunc(sysdate) 
    then trunc(sysdate) else trunc(so.revised_due_date) end) due_date, 
(case 
    when (case when sp.pr_typ in ('VV','VD') then 'DVD' when sp.pr_typ in ('RD','CD') 
    then 'CD' end) = 'CD' 
    and  (case when so.tec_criteria in ('PI','MC') 
    then 'XX' else so.tec_criteria end) = 'OF'
    then sum(so.revised_qty_due)
end) CD_OF_VOLUME
from shop_order so
left join scm_prodtyp sp
on so.prodtyp = sp.prodtyp
where so.order_type = 'MD' 
and so.plant = 'W' 
and so.status_code between '4' and '8' 
and trunc(so.revised_due_date) <= trunc(sysdate)+30
group by trunc(so.revised_due_date), so.tec_criteria, sp.pr_typ
order by trunc(so.revised_due_date)

The problem I have is where there is a date with no production planned, the date wont appear on the report. Is there a way of filling in the missing dates.

i.e. the current report shows the following ...

DUE_DATE    CD_OF_VOLUME 
14/04/2015     35,267.00 
15/04/2015     71,744.00 
16/04/2015     20,268.00 
17/04/2015     35,156.00 
18/04/2015     74,395.00 
19/04/2015      3,636.00 
21/04/2015      5,522.00
22/04/2015     15,502.00
04/05/2015     10,082.00

Note: missing dates (20/04/2015, 23/04/2015 to 03/05/2015)

Range is always for a thirty day period from sysdate. How do you fill in the missing dates? Do you need some kind of calendar table?

Thanks

Upvotes: 3

Views: 8174

Answers (3)

void
void

Reputation: 7890

you can get the gaps by using connect by and a left join:

assuming your schema is:

create table tbl(DUE_DATE date, CD_OF_VOLUME float);
insert into tbl values(to_date('14/04/2015','DD/MM/YYYY'),35267.00);
insert into tbl values(to_date('15/04/2015','DD/MM/YYYY'),71744.00); 
insert into tbl values(to_date('16/04/2015','DD/MM/YYYY'),20268.00); 
insert into tbl values(to_date('17/04/2015','DD/MM/YYYY'),35156.00); 
insert into tbl values(to_date('18/04/2015','DD/MM/YYYY'),74395.00); 
insert into tbl values(to_date('19/04/2015','DD/MM/YYYY'),3636.00); 
insert into tbl values(to_date('21/04/2015','DD/MM/YYYY'),5522.00);
insert into tbl values(to_date('22/04/2015','DD/MM/YYYY'),15502.00);
insert into tbl values(to_date('04/05/2015','DD/MM/YYYY'),10082.00);

you can say:

with cte as
(
  select (select min(DUE_DATE)-1 from tbl)+ level as dt
  from dual
  connect by level <= (select max(DUE_DATE)-min(DUE_DATE) from tbl)
)
select to_char(c.dt,'DD/MM/YYYY') gap,null volume 
from cte c
left join tbl t on c.dt=t.DUE_DATE
where t.DUE_DATE is null
order by c.dt

Result:

GAP         VOLUME
20/04/2015  (null)
23/04/2015  (null)
24/04/2015  (null)
25/04/2015  (null)
26/04/2015  (null)
27/04/2015  (null)
28/04/2015  (null)
29/04/2015  (null)
30/04/2015  (null)
01/05/2015  (null)
02/05/2015  (null)
03/05/2015  (null)

Notice: you can implement this in your original query, one simplest way is to wrap your query and use it as a subquery instead of tbl in above code snippet.

Upvotes: 1

David Faber
David Faber

Reputation: 12495

You can get the 30-day period from SYSDATE as follows (I assume you want to include SYSDATE?):

WITH mydates AS (
    SELECT TRUNC(SYSDATE) - 1 + LEVEL AS due_date FROM dual
   CONNECT BY LEVEL <= 31
)

Then use the above to do a LEFT JOIN with your query (perhaps not a bad idea to put your query in a CTE as well):

WITH mydates AS (
    SELECT TRUNC(SYSDATE) - 1 + LEVEL AS due_date FROM dual
   CONNECT BY LEVEL <= 31
), myorders AS (
    select 
    (case when trunc(so.revised_due_date) <= trunc(sysdate) 
        then trunc(sysdate) else trunc(so.revised_due_date) end) due_date, 
    (case 
        when (case when sp.pr_typ in ('VV','VD') then 'DVD' when sp.pr_typ in ('RD','CD') 
        then 'CD' end) = 'CD' 
        and  (case when so.tec_criteria in ('PI','MC') 
        then 'XX' else so.tec_criteria end) = 'OF'
        then sum(so.revised_qty_due)
    end) CD_OF_VOLUME
    from shop_order so
    left join scm_prodtyp sp
    on so.prodtyp = sp.prodtyp
    where so.order_type = 'MD' 
    and so.plant = 'W' 
    and so.status_code between '4' and '8' 
    and trunc(so.revised_due_date) <= trunc(sysdate)+30
    group by trunc(so.revised_due_date), so.tec_criteria, sp.pr_typ
    order by trunc(so.revised_due_date)
)
SELECT mydates.due_date, myorders.cd_of_volume
  FROM mydates LEFT JOIN myorders
    ON mydates.due_date = myorders.due_date;

If you want to show a zero on "missing" dates instead of a NULL, use COALESCE(myorders.cd_of_volume, 0) AS cd_of_volume above.

Upvotes: 5

Hard Worker
Hard Worker

Reputation: 1121

what you can do is this : creating a new table with all the days you need .

WITH DAYS AS
(SELECT TRUNC(SYSDATE) - ROWNUM DDD
 FROM ALL_OBJECTS
 WHERE ROWNUM < 365)
SELECT
  DAYS.DDD
FROM

  DAYS;

then full outer join between thoes table :

select  DUE_DATE ,   CD_OF_VOLUME , DDD
from (
    select 
    (case when trunc(so.revised_due_date) <= trunc(sysdate) 
        then trunc(sysdate) else trunc(so.revised_due_date) end) due_date, 
    (case 
        when (case when sp.pr_typ in ('VV','VD') then 'DVD' when sp.pr_typ in ('RD','CD') 
        then 'CD' end) = 'CD' 
        and  (case when so.tec_criteria in ('PI','MC') 
        then 'XX' else so.tec_criteria end) = 'OF'
        then sum(so.revised_qty_due)
    end) CD_OF_VOLUME
    from shop_order so
    left join scm_prodtyp sp
    on so.prodtyp = sp.prodtyp
    where so.order_type = 'MD' 
    and so.plant = 'W' 
    and so.status_code between '4' and '8' 
    and trunc(so.revised_due_date) <= trunc(sysdate)+30
    group by trunc(so.revised_due_date), so.tec_criteria, sp.pr_typ
    order by trunc(so.revised_due_date)
)  full outer join   NEW_TABLE new on (    new .DDD =  DUE_DATE     )
  where new .DDD between   /* */   AND   /* */   /*   pick your own limit) */

Upvotes: 1

Related Questions