如何根据oracle中的日期范围对日期列进行分组

时间:2016-08-02 19:03:57

标签: stored-procedures oracle11g

我有一个表格,其中包含有关产品的反馈。它有反馈类型(正面,负面),这是一个文本列,即发表评论的日期。我需要获得特定时间段的积极,负面反馈的总数。例如,如果日期范围是30天,我需要获得4周的正反馈总数,如果日期范围是6个月,我需要得到每个月的正反馈总数。如何根据日期对计数进行分组。

+------+------+----------+----------+---------------+--+--+--+
| Slno | User | Comments |   type   | commenteddate |  |  |  |
+------+------+----------+----------+---------------+--+--+--+
|    1 | a    | aaaa     | positive | 22-jun-2016   |  |  |  |
|    2 | b    | bbb      | positive | 1-jun-2016    |  |  |  |
|    3 | c    | qqq      | negative | 2-jun-2016    |  |  |  |
|    4 | d    | ccc      | neutral  | 3-may-2016    |  |  |  |
|    5 | e    | www      | positive | 2-apr-2016    |  |  |  |
|    6 | f    | s        | negative | 11-nov-2015   |  |  |  |
+------+------+----------+----------+---------------+--+--+--+

我试过的查询是

SELECT类型,to_char(注释日期,'DD-MM-YYYY'),Count(类型)FROM注释GROUP BY类型,to_char(注释日期,'DD-MM-YYYY');

1 个答案:

答案 0 :(得分:0)

这是对罐头的一击......

假设:

  • 您希望能够将分组切换为每周或每月一次
  • 第一个期间的开始将是反馈数据中的第一个日期;间隔将从此初始日期计算
  • 输出将显示反馈值,时间段,计数
  • 时间段不会重叠,因此句点将是x - > x +间隔 - 1天
  • 时间不重要(评论日期的时间始终为00:00:00)

首先,创建一些样本数据(100行):

drop table product_feedback purge;

create table product_feedback
as
select rownum as slno
, chr(65 + MOD(rownum, 26)) as userid
, lpad(chr(65 + MOD(rownum, 26)), 5, chr(65 + MOD(rownum, 26))) as comments
, trunc(sysdate) + rownum + trunc(dbms_random.value * 10) as commented_date
, case mod(rownum * TRUNC(dbms_random.value * 10), 3) 
       when 0 then 'positive' 
       when 1 then 'negative' 
       when 2 then 'neutral' end as feedback
from dual
connect by level <= 100
;

以下是我的示例数据:

select *
from product_feedback
;

SLNO    USERID  COMMENTS    COMMENTED_DATE  FEEDBACK
1   B   BBBBB   2016-08-06  neutral
2   C   CCCCC   2016-08-06  negative
3   D   DDDDD   2016-08-14  positive
4   E   EEEEE   2016-08-16  negative
5   F   FFFFF   2016-08-09  negative
6   G   GGGGG   2016-08-14  positive
7   H   HHHHH   2016-08-17  positive
8   I   IIIII   2016-08-18  positive
9   J   JJJJJ   2016-08-12  positive
10  K   KKKKK   2016-08-15  neutral
11  L   LLLLL   2016-08-23  neutral
12  M   MMMMM   2016-08-19  positive
13  N   NNNNN   2016-08-16  neutral
...

现在为有趣的部分。这是要点:

  • 找出数据中最早和最新评论日期的内容
  • 包含一个查询,您可以在其中设置时间段(“周”或“月”)
  • 生成最小/最大日期之间的所有(每周或每月)时间段
  • 将产品反馈加入时间段(开始和结束之间的评论日期)与外部联接,以防您想要查看所有时间段是否有任何反馈
  • 通过反馈,期间开始和期间结束对连接结果进行分组,并设置一列来计算3个可能的反馈值之一

X

with 
min_max_dates -- get earliest and latest feedback dates
as
(select min(commented_date) min_date, max(commented_date) max_date
from product_feedback
)
, time_period_interval
as
(select 'MONTHS' as tp_interval -- set the interval/time period here
   from dual
)
, -- generate all time periods between the start date and end date
time_periods (start_of_period, end_of_period, max_date, time_period) -- recursive with clause - fun stuff!
as
(select mmd.min_date as start_of_period
      , CASE WHEN tpi.tp_interval = 'WEEKS' 
             THEN mmd.min_date + 7
             WHEN tpi.tp_interval = 'MONTHS' 
             THEN ADD_MONTHS(mmd.min_date, 1)
             ELSE NULL
        END - 1 as end_of_period
, mmd.max_date
, tpi.tp_interval as time_period
   from time_period_interval tpi
        cross join
        min_max_dates mmd
UNION ALL
select CASE WHEN time_period = 'WEEKS' 
             THEN start_of_period + 7 * (ROWNUM )
             WHEN time_period = 'MONTHS' 
             THEN ADD_MONTHS(start_of_period, ROWNUM)
             ELSE NULL
        END as start_of_period
, CASE WHEN time_period = 'WEEKS' 
             THEN start_of_period + 7 * (ROWNUM + 1)
             WHEN time_period = 'MONTHS' 
             THEN ADD_MONTHS(start_of_period, ROWNUM + 1)
             ELSE NULL
        END - 1 as end_of_period
, max_date
, time_period
from time_periods
where end_of_period <= max_date 
)
-- now put it all together
select pf.feedback
     , tp.start_of_period
     , tp.end_of_period
     , count(*) as feedback_count
from time_periods tp
left outer join
product_feedback pf
on pf.commented_date between tp.start_of_period and tp.end_of_period
group by tp.start_of_period
       , tp.end_of_period
       , pf.feedback
order by pf.feedback
       , tp.start_of_period
;

输出:

negative    2016-08-06  2016-09-05  6
negative    2016-09-06  2016-10-05  7
negative    2016-10-06  2016-11-05  8
negative    2016-11-06  2016-12-05  1
neutral     2016-08-06  2016-09-05  6
neutral     2016-09-06  2016-10-05  5
neutral     2016-10-06  2016-11-05  11
neutral     2016-11-06  2016-12-05  2
positive    2016-08-06  2016-09-05  17
positive    2016-09-06  2016-10-05  16
positive    2016-10-06  2016-11-05  15
positive    2016-11-06  2016-12-05  6

- 编辑 -

新的和改进的,一体化易于使用的程序。 (我假设您可以配置程序以您需要的任何方式使用查询。)我做了一些更改以简化几个地方的CASE语句并注意无论出于何种原因在主SELECT中使用LEFT OUTER JOIN导致ORA-600错误,因此我将其切换为INNER JOIN。

CREATE OR REPLACE PROCEDURE feedback_counts(p_days_chosen IN NUMBER, p_cursor OUT SYS_REFCURSOR)
AS
BEGIN

OPEN p_cursor FOR
    with 
    min_max_dates -- get earliest and latest feedback dates
    as
    (select min(commented_date) min_date, max(commented_date) max_date
    from product_feedback
    )
    , time_period_interval
    as
    (select CASE 
             WHEN p_days_chosen BETWEEN 1 AND 10 THEN 'DAYS'
             WHEN p_days_chosen > 10 AND p_days_chosen <=31 THEN 'WEEKS'
             WHEN p_days_chosen > 31 AND p_days_chosen <= 365 THEN 'MONTHS'
             ELSE '3-MONTHS'
        END as tp_interval -- set the interval/time period here
        from dual --(SELECT p_days_chosen as days_chosen from dual)
    )    
    , -- generate all time periods between the start date and end date
    time_periods (start_of_period, end_of_period, max_date, tp_interval) -- recursive with clause - fun stuff!
    as
    (select mmd.min_date as start_of_period
          , CASE tpi.tp_interval 
                 WHEN 'DAYS'
                 THEN mmd.min_date + 1
                 WHEN 'WEEKS' 
                 THEN mmd.min_date + 7
                 WHEN 'MONTHS' 
                 THEN mmd.min_date + 30
                 WHEN '3-MONTHS'
                 THEN mmd.min_date + 90
                 ELSE NULL
            END - 1 as end_of_period
    , mmd.max_date
    , tpi.tp_interval
       from time_period_interval tpi
            cross join
            min_max_dates mmd
    UNION ALL
    select CASE tp_interval
                 WHEN 'DAYS'
                 THEN start_of_period + 1 * ROWNUM
                 WHEN 'WEEKS' 
                 THEN start_of_period + 7 * ROWNUM 
                 WHEN 'MONTHS' 
                 THEN start_of_period + 30 * ROWNUM
                 WHEN '3-MONTHS'
                 THEN start_of_period + 90 * ROWNUM
                 ELSE NULL
            END as start_of_period
        , start_of_period
          + CASE tp_interval 
                 WHEN 'DAYS'
                 THEN 1
                 WHEN 'WEEKS' 
                 THEN 7
                 WHEN 'MONTHS' 
                 THEN 30
                 WHEN '3-MONTHS'
                 THEN 90
                 ELSE NULL
            END * (ROWNUM + 1) 
            - 1 as end_of_period
    , max_date
    , tp_interval
    from time_periods
    where end_of_period <= max_date 
    )
    -- now put it all together
    select pf.feedback
         , tp.start_of_period
         , tp.end_of_period
         , count(*) as feedback_count
    from time_periods tp
    inner join -- currently a bug that prevents the procedure from compiling with a LEFT OUTER JOIN
    product_feedback pf
    on pf.commented_date between tp.start_of_period and tp.end_of_period
    group by tp.start_of_period
           , tp.end_of_period
           , pf.feedback
    order by tp.start_of_period
           , pf.feedback
    ;
END; 

测试程序(类似于SQLPlus或SQL Developer):

var x refcursor
exec feedback_counts(10, :x)
print :x