如何分组指定年份和月份的范围?

时间:2012-09-03 04:26:26

标签: sql oracle plsql report procedure

我有两组表的大量数据,我希望在指定的日期范围内显示,并根据当前日期按功能分组。数据范围是:

让我们说今天的日期是03/09/2012(DD / MM / YYYY)

--Product 1--
Everything 5 years ago 'Before 2007'  {field1}   {field2}   {field3} 
4 years ago          '2008' 
3 years ago          '2009' 
2 years ago          '2010' 
1 year by month      'Jan 2011' 
'Feb 2011' 
'Mar 2011 
.... 
.... 
'Dec 2011' 
Sum of 1 year ago    '2011' 
This year by month   'Jan 2012' 
'Feb 2012' 
'Mar 2012' 
.... 
....
'Sept 2012' 
Sum of this year '2012'

这个sql的性能很重要。到目前为止,我得到了一个sql,可以按年份或月份对每个产品进一步分组,但不是按照上面的顺序。我在考虑使用NVL,CASE和许多嵌套的SQL,但是任何人都可以想到一个可以获得良好性能的解决方案吗?

    SELECT EXTRACT (YEAR FROM {DATE}) "YEAR", EXTRACT (MONTH FROM {DATE}) "MONTH", SUM({field1}) as A, SUM({field 2}) as B ,COUNT(1) as {field 3}
    FROM (
            SELECT {Field A}, DECODE({Field Key1}, NULL, 0, 1) {field 1}, DECODE({field B}, NULL, 1, 0)  {field2}, {Field Key2}
            FROM {table A}, (
                    SELECT {field key2}
                    FROM {table B}
                    WHERE {conditions} B  
            WHERE A.KEY= B.KEY(+)
    )
    where {conditions}
    GROUP BY EXTRACT (YEAR FROM {DATE}) , EXTRACT (MONTH FROM {DATE}) 
  ) DATASET   

2 个答案:

答案 0 :(得分:1)

我有点不清楚你正在努力解决哪个部分,或者为什么要引用许多嵌套的SQL。没有任何样本数据,我不得不做些准备:

create table t42 (my_date date, my_value number);

insert into t42 values (date '2006-01-31', 0601);
insert into t42 values (date '2006-12-31', 0612);
insert into t42 values (date '2007-01-31', 0701);
insert into t42 values (date '2007-12-31', 0712);
insert into t42 values (date '2008-01-31', 0801);
insert into t42 values (date '2008-12-31', 0812);
insert into t42 values (date '2009-01-31', 0901);
insert into t42 values (date '2009-12-31', 0912);
insert into t42 values (date '2010-01-31', 1001);
insert into t42 values (date '2010-12-31', 1012);
insert into t42 values (date '2011-01-31', 1101);
insert into t42 values (date '2011-12-31', 1112);
insert into t42 values (date '2012-01-31', 1201);
insert into t42 values (date '2012-02-29', 1202);
insert into t42 values (date '2012-03-31', 1203);
insert into t42 values (date '2012-04-30', 1204);
insert into t42 values (date '2012-05-31', 1205);

然后,您可以使用内部查询生成句点/年/月的“标签”,并使用虚拟字段对结果进行排序,加上您实际感兴趣的值。然后使用外部查询来执行任何sumcount等。

select label, sum(my_value), count(1)
from
(
    select
        case when my_date < trunc(sysdate, 'YYYY') - interval '4' year then
                'Before ' || to_char(trunc(sysdate, 'YYYY')
                - interval '5' year, 'YYYY')
            when my_date < trunc(sysdate, 'YYYY') - interval '1' year then
                to_char(my_date, 'YYYY')
            else to_char(my_date, 'Mon YYYY')
        end as label,
        case when my_date < trunc(sysdate, 'YYYY') - interval '4' year then
                to_char(trunc(sysdate, 'YYYY') - interval '5' year, 'YYYYMM')
            when my_date < trunc(sysdate, 'YYYY') - interval '1' year then
                to_char(my_date, 'YYYY') || '01'
            else to_char(my_date, 'YYYYMM')
        end as order_field,
        my_value
    from t42
)
group by label, order_field
order by order_field;

我正在使用trunc(sysdate, 'YYYY')查找当前年份的开头,然后使用interval返回五年,并生成labelorder_field psuedo -columns基于那些'桶'。使用这样的case可以让我拥有不同的桶 - 一个用于超过五年的所有东西,一年到去年一年,从那时起每个月一个。

LABEL             SUM(MY_VALUE)   COUNT(1)
----------------- ------------- ----------
Before 2007                2626          4
2008                       1613          2
2009                       1813          2
2010                       2013          2
Jan 2011                   1101          1
Dec 2011                   1112          1
Jan 2012                   1201          1
Feb 2012                   1202          1
Mar 2012                   1203          1
Apr 2012                   1204          1
May 2012                   1205          1

我只打了一次桌子,所以性能应该取决于你如何提取原始数据(你的连接和条件),而不是你如何操纵它。显然,您可以将t42替换为两个表之间的当前连接,并提取您感兴趣的字段。

我建议您切换到ANSI连接语法,而不是Oracle的外部连接的旧(+)表示法。这并不涉及没有任何数据的任何年份或月份,但您的原始大纲也没有,因此这可能不是问题。您如何制作或至少显示“年度总和”值可能取决于您的客户。


将标签生成分离到视图中以使其可重复使用,并允许您查找没有数据的句点:

create or replace view v42 (period_label, period_order, period_start, period_end)
as
select 'Before ' || to_char(trunc(sysdate, 'YYYY') - interval '5' year, 'YYYY'),
    '197001',
    date '1970-01-01',
    trunc(sysdate, 'YYYY') - interval '4' year  - interval '1' second
from dual
union
select to_char(year_start, 'YYYY'),
    to_char(year_start, 'YYYY') || '01',
    year_start,
    year_start + interval '1' year - interval '1' second
from (
    select add_months(trunc(sysdate, 'YYYY'), - 12 * (level + 1)) as year_start
    from dual connect by level <= 3
)
union
select to_char(month_start, 'Mon YYYY'),
    to_char(month_start, 'YYYYMM'),
    month_start,
    month_start + interval '1' month - interval '1' second
from (
    select add_months(trunc(sysdate, 'MM'), 1 - level) as month_start
    from dual connect by level <= 12 + to_number(to_char(sysdate, 'MM'))
);

这是为了生产你原来的标签;如果您希望单独显示所有年份,请删除union的第一部分,并且您可以调整connect by条款以更改按月显示的年份。 (你可以参数化,但这可能会有点远)。

你有三个类别的“桶”,一个按月分类,一个按年分类,然后是过去任何东西的全能类;联合的每个部分都解决其中一个问题,为每个类中的所有存储桶生成周期开始和结束日期,加上标签以及稍后要订购的内容。查看视图,也可以单独查看每个select,看看他们正在做什么。

然后将该视图加入到您的数据表或表中;如果要显示没有匹配数据的标签,请使用左外连接:

select v.period_label, nvl(sum(t.my_value), 0), count(t.my_value)
from v42 v
left join t42 t on t.my_date between v.period_start and v.period_end
group by v.period_label, v.period_order
order by v.period_order;

PERIOD_LABEL      NVL(SUM(T.MY_VALUE),0) COUNT(T.MY_VALUE)
----------------- ---------------------- -----------------
Before 2007                         2626                 4
2008                                1613                 2
2009                                1813                 2
2010                                2013                 2
Jan 2011                            1101                 1
Feb 2011                               0                 0
...
Nov 2011                               0                 0
Dec 2011                            1112                 1
...

答案 1 :(得分:0)

select * from (
  select t.*
         ,case 
            when extract(YEAR FROM t.date) < 2007
              then 0
            else
              extract(YEAR FROM t.date)
          end as nYear
         ,case
            when extract(YEAR FROM t.date) < 2007
              then 0
            else
              extract(MONTH FROM t.date)
          end as nMonth       
  from table_name t
)d
group by d.nYear, d.nMonth