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.