如何在Oracle查询中填写缺少的月份数据

时间:2015-09-14 23:59:21

标签: sql oracle oracle11g

这与Mysql to select month-wise record even if data not existHow to fill in missing months?有关,但对于Oracle和其他数据。我想使用Crystal Reports交叉表,但需要零数据才能获得这几个月的列(参见Keeping same number of columns at cross tab report

我的查询返回

等数据
  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

我想为每个NewRate制作交叉表,但为了获得每个月的列,我需要返回其他行,并记录每个NewRate值和所有月份。所以,我需要一个查询来获取以下附加记录(OldRate只是每个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

我当前的查询具有特定日期,而不仅仅是月份编号,这可能会更好一些。我愿意使用1-1-2015,2-1-2015之类的日期,如果已经有1月5日,那么添加一个带0计数的Jan 1将不会受到影响。

但是,我不想为不存在的Rate分组添加记录。将Rate1改为Rate2和Rate1改为Rate3将全部为零。但由于原始数据集没有Rate3到Rate2,我不想添加它们。费率是Oracle程序的参数,日期范围(也是参数)默认为当前日历年。

2 个答案:

答案 0 :(得分:3)

您可以使用partitioned outer join来消除交叉连接。

感谢@Wolf SQL Fiddle

查询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 |

答案 1 :(得分:2)

您希望 densify 您的数据,并且有很多关于如何执行此操作的帖子和答案。基本上你需要生成一组所有可能的行,然后将你的实际数据左外连接到理论集。

在此示例中,我使用CONNECT BY子句生成一组月份。您提到您的实际数据集使用的是实际日期,因此我在此示例中使用了日期类型。

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

我在WITH子句中实现了这些日期(以及您的示例数据),但您也可以在内联视图中执行相同操作。

然后在主查询中,您可以使用这些密集日期并将它们与唯一的一组费率组合交叉连接,以创建一组实际费率集和日期的所有可能组合。为此,我们会LEFT OUTER JOIN您的实际数据,使用您的默认值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;

这是一个SQL Fiddle工作示例。