简体   繁体   中英

How to fill in missing month data in an Oracle query

This is related to Mysql to select month-wise record even if data not exist and How to fill in missing months? , but for Oracle and with additional data. I want to use a Crystal Reports Crosstab, but need zeros for data in order to get columns for the months (see Keeping same number of columns at cross tab report )

My query returns data such as

  NewRate   OldRate   Month   Count   
   Rate1     Rate2     8        1  
   Rate1     Rate3     2        3
   Rate1     Rate3     3        2
   Rate1     Rate3     7        2
   Rate1     Rate3     8       12
   Rate3     Rate1     1        1
   Rate3     Rate1     2        1
   Rate3     Rate1     5        1
   Rate3     Rate1     7        3
   Rate3     Rate1     8        9

I want to make a crosstab for each NewRate, but in order to get a column for each month, I need to have additional rows returned, with a record for each NewRate value and all months. So, I'd need a query to get me the following additional records (with OldRate being just one of the options for each NewRate)

   NewRate   OldRate   Month   Count   
   Rate1     Rate2     1        0  
   Rate1     Rate2     2        0
   Rate1     Rate2     3        0
   Rate1     Rate2     4        0
   Rate1     Rate2     5        0
   Rate1     Rate2     6        0
   Rate1     Rate2     7        0
   Rate1     Rate2     9        0
   Rate1     Rate2    10        0
   Rate1     Rate2    11        0
   Rate1     Rate2    12        0
   Rate3     Rate1     3        0
   Rate3     Rate1     4        0
   Rate3     Rate1     6        0
   Rate3     Rate1     9        0
   Rate3     Rate1    10        0
   Rate3     Rate1    11        0
   Rate3     Rate1    12        0

My current query has specific dates, not just the month number, which probably works a bit better. I'm willing to use dates such as 1-1-2015, 2-1-2015, etc, and if there is already a Jan 5, adding a Jan 1 with a 0 count won't hurt.

But, I don't want to add records for Rate groupings that don't exist. It would be fine to have a Rate1 to Rate2 and Rate1 to Rate3, all with zeros. But since the original data set doesn't have a Rate3 to Rate2, I wouldn't want to add those. The rates are parameters to the Oracle procedure, and the date range (also a parameter) defaults to the current calendar year.

You can use partitioned outer join for this, that will eliminate the cross join.

Thanks @Wolf for the SQL Fiddle

Query 1 :

with 
-- Your sample data
sample_data as (   
   select 'Rate1' NewRate, 'Rate2' OldRate, to_date(8, 'MM') Month,  1 Count from dual union all  
   select 'Rate1', 'Rate3', to_date(2, 'MM'),  3 from dual union all
   select 'Rate1', 'Rate3', to_date(3, 'MM'),  2 from dual union all
   select 'Rate1', 'Rate3', to_date(7, 'MM'),  2 from dual union all
   select 'Rate1', 'Rate3', to_date(8, 'MM'), 12 from dual union all
   select 'Rate3', 'Rate1', to_date(1, 'MM'),  1 from dual union all
   select 'Rate3', 'Rate1', to_date(2, 'MM'),  1 from dual union all
   select 'Rate3', 'Rate1', to_date(5, 'MM'),  1 from dual union all
   select 'Rate3', 'Rate1', to_date(7, 'MM'),  3 from dual union all
   select 'Rate3', 'Rate1', to_date(8, 'MM'),  9 from dual),

-- Using the "with clause" we can generate a set of months as rows
dense_months as (
   select to_date(level, 'MM') mo 
   from dual
   connect by level <=12)

select 
   sd.newrate, sd.oldrate, dm.mo, nvl(sd.count,0) count
from sample_data sd
partition by (sd.newrate, sd.oldrate)
right outer join dense_months dm 
on dm.mo = sd.month
order by 1, 2, 3

Results :

| NEWRATE | OLDRATE |                          MO | COUNT |
|---------|---------|-----------------------------|-------|
|   Rate1 |   Rate2 |   January, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |  February, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |     March, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |     April, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |       May, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |      June, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |      July, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |    August, 01 2015 00:00:00 |     1 |
|   Rate1 |   Rate2 | September, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |   October, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |  November, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate2 |  December, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate3 |   January, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate3 |  February, 01 2015 00:00:00 |     3 |
|   Rate1 |   Rate3 |     March, 01 2015 00:00:00 |     2 |
|   Rate1 |   Rate3 |     April, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate3 |       May, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate3 |      June, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate3 |      July, 01 2015 00:00:00 |     2 |
|   Rate1 |   Rate3 |    August, 01 2015 00:00:00 |    12 |
|   Rate1 |   Rate3 | September, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate3 |   October, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate3 |  November, 01 2015 00:00:00 |     0 |
|   Rate1 |   Rate3 |  December, 01 2015 00:00:00 |     0 |
|   Rate3 |   Rate1 |   January, 01 2015 00:00:00 |     1 |
|   Rate3 |   Rate1 |  February, 01 2015 00:00:00 |     1 |
|   Rate3 |   Rate1 |     March, 01 2015 00:00:00 |     0 |
|   Rate3 |   Rate1 |     April, 01 2015 00:00:00 |     0 |
|   Rate3 |   Rate1 |       May, 01 2015 00:00:00 |     1 |
|   Rate3 |   Rate1 |      June, 01 2015 00:00:00 |     0 |
|   Rate3 |   Rate1 |      July, 01 2015 00:00:00 |     3 |
|   Rate3 |   Rate1 |    August, 01 2015 00:00:00 |     9 |
|   Rate3 |   Rate1 | September, 01 2015 00:00:00 |     0 |
|   Rate3 |   Rate1 |   October, 01 2015 00:00:00 |     0 |
|   Rate3 |   Rate1 |  November, 01 2015 00:00:00 |     0 |
|   Rate3 |   Rate1 |  December, 01 2015 00:00:00 |     0 |

You are looking to densify your data, and there are many posts and answers on how to do this. Essentially you need to generate a set of all possible rows, and then left outer join your actual data to the theoretical set.

In this example, I'm using the CONNECT BY clause to generate a set of months. You mentioned that your actual data set uses actual dates, so I'm using a date type in this example.

select to_date(level, 'MM') mo 
from dual
connect by level <=12

I am materializing those dates in a WITH clause (along with your sample data), but you can do the same in an inline view as well.

Then in the main query, you can take those dense dates and cross join them with the unique set of rate combinations to create a set of all possible combinations of actual rate sets and dates. To this, we then LEFT OUTER JOIN your actual data, filling in the missing counts with your default of 0 .

with 
-- Your sample data
sample_data as (   
   select 'Rate1' NewRate, 'Rate2' OldRate, to_date(8, 'MM') Month,  1 Count from dual union all  
   select 'Rate1', 'Rate3', to_date(2, 'MM'),  3 from dual union all
   select 'Rate1', 'Rate3', to_date(3, 'MM'),  2 from dual union all
   select 'Rate1', 'Rate3', to_date(7, 'MM'),  2 from dual union all
   select 'Rate1', 'Rate3', to_date(8, 'MM'), 12 from dual union all
   select 'Rate3', 'Rate1', to_date(1, 'MM'),  1 from dual union all
   select 'Rate3', 'Rate1', to_date(2, 'MM'),  1 from dual union all
   select 'Rate3', 'Rate1', to_date(5, 'MM'),  1 from dual union all
   select 'Rate3', 'Rate1', to_date(7, 'MM'),  3 from dual union all
   select 'Rate3', 'Rate1', to_date(8, 'MM'),  9 from dual),

-- Using the "with clause" we can generate a set of months as rows
dense_months as (
   select to_date(level, 'MM') mo 
   from dual
   connect by level <=12)

select 
   rg.newrate, rg.oldrate, dm.mo, nvl(sd.count,0) count
-- Here we are creating a cartesian product of your rate groups and twelve calendar months
from dense_months dm
cross join 
  (select distinct newrate, oldrate
   from sample_data) rg
-- Then we can left join our actual data to the cartesian product.
left outer join sample_data sd on rg.newrate = sd.newrate and rg.oldrate = sd.oldrate and dm.mo = sd.month
order by 1, 2, 3;

Here is a SQL Fiddle working example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM