【问题标题】:How to group by specify range of years and month?如何按指定的年份和月份范围进行分组?
【发布时间】:2012-09-03 04:26:25
【问题描述】:

我有来自两个表的一系列产品的大量数据,我想根据当前日期在指定的日期范围内显示这些数据,并按功能分组。数据范围为:

假设今天的日期是 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   

【问题讨论】:

    标签: sql oracle plsql report procedure


    【解决方案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 伪列.像这样使用 case 可以让我拥有不同的存储桶 - 一个用于超过 5 年的所有东西,一个用于直到去年的每一年,一个用于此后的每个月。

    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
    ...
    

    【讨论】:

    • 对不起,这是我的第一篇文章。我的预言机技能生锈了,几个月前才重新拿起它。感谢您的详细解释和建议。您的解决方案解决了我 90% 的问题。我仍然在努力解决一些细节。 1:有 20 多个产品,每个产品都需要一个外观完全相同的结果表。 2:并非所有产品都有'my_date'值来绘制标签。 3:最近3年需要总计,但可以在水晶报表上计算。这基本上是一个 OLAP 多维数据集分析产品销售的特定时期,必须在水晶报表中写入 SP n 显示。
    • @thzNewbie - 这并不是真正的批评,更多的是我不确定我是否真的解决了你的问题 - 很高兴我似乎是,无论如何。如果“桶”是您需要的,那么我想您的主要问题是将基础数据放入正确的形式和位置 - 将 from t42 替换为 from (select ... from table_a join table_b ... where ...) 等。当然,您也可以拥有更多列只需要将它们放在您的内部选择中,然后聚合它们或将它们添加到group by
    • 任何解决标签问题的解决方案,因此它总是创建一个固定的视图?除了使用我想要的指定日期范围的组合创建一个表。即生成这个年月组合列表
    • @thzNewbie - 我不太清楚你的意思;使相同的标签结构可用于其他查询,而无需复制代码;包括没有数据的月/年标签;两个都;还是别的什么?
    • 创建具有更好编码的标签结构。我创建了一个带有产品数量(P)的标签结构(L),所以我得到 PxN 行,然后加入不为空的产品并将空替换为 0。我将在水晶报告上编写一些公式来对每个产品进行分类。对于标签: SELECT TO_CHAR(EXTRACT (YEAR FROM SYSDATE - INTERVAL '9' YEAR )) LABEL_YEAR , TO_CHAR(TRUNC(SYSDATE, 'YYYY') - INTERVAL '9' YEAR , 'YYYYMM') IND FROM DUAL UNION SELECT TO_CHAR(EXTRACT (YEAR FROM SYSDATE - INTERVAL '8' YEAR )) LABEL_YEAR, TO_CHAR(TRUNC(SYSDATE, 'YYYY') - INTERVAL '8' YEAR , 'YYYYMM') IND FROM DUAL 等
    【解决方案2】:
    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-07
      • 2012-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多