在开始时间和结束时间之间按比例分配数量

时间:2014-03-06 10:01:12

标签: oracle

简化,我有这样的表:

start_date           end_date             units
06/03/2014 14:00:00  06/03/2014 15:30:00  300

我想要做的是找出持续时间(end_date - start_date)。将其拆分为半小时间隔,然后在这些间隔之间平均分配单位。

在这种情况下,持续时间为90分钟,间隔3个半小时,即每个间隔100个单位。

由此我想在这些方面创造一些东西:

interval_date        units
06/03/2014 14:00:00  100
06/03/2014 14:30:00  100
06/03/2014 15:00:00  100

我有这个工作,但它涉及多个子查询嵌套(可能可以清理一点使用更少)。它还涉及间隔的硬编码列,而不是一列来保持不同的间隔。例如,它看起来像这样:

id  d0000  ...  d1400  d1430  d1500  d1530  d1600 ... d2330
1   0           100    100    100    0      0         0

因此,在我想改变间隔持续时间(15分钟,1小时等)的情况下,它不是很灵活。

非常感谢任何想法或帮助。

3 个答案:

答案 0 :(得分:2)

试试这个:

with t as (
select to_date('06/03/2014 14:00:00', 'dd/mm/yyyy HH24:mi:ss') start_time,
       to_date('06/03/2014 15:10:00', 'dd/mm/yyyy HH24:mi:ss') end_time,
       300 units
  from dual
)
select start_time + (rownum - 1) / 48 start_interval,
       units / ceil((end_time - start_time) * 48) units
  from t
  where start_date <> end_date
connect by level <= ceil((end_time - start_time) * 48);

START_INTERVAL             UNITS
--------------             ----------
06.03.2014 14:00:00        100
06.03.2014 14:30:00        100
06.03.2014 15:00:00        100

更新:添加了WHERE条件,以便在start_date等于end_date时避免除以零。

答案 1 :(得分:2)

如果您使用的是Oracle 11gR2,则可以使用递归CTE。

with x(start_date, end_date, units) as (
  --select all the records and units per half an hour.
  select start_date, end_date, units/((end_date - start_date)*48)
  from myt
  union all
  --iteratively add 30 minutes to start_date till it stays less than end_date
  select start_date + interval '30' minute, end_date, units
  from x
  where start_date + interval '30' minute < end_date
  )
select start_date, units
from x
order by 1;

架构:

create table myt(
  start_date date,
  end_date   date,
  units      number
  );

insert into myt values(to_date('06/03/2014 14:00:00','mm/dd/yyyy hh24:mi:ss'),
                       to_date('06/03/2014 15:30:00','mm/dd/yyyy hh24:mi:ss'),
                       300
                       );

insert into myt values(to_date('06/04/2014 11:00:00','mm/dd/yyyy hh24:mi:ss'),
                       to_date('06/04/2014 15:30:00','mm/dd/yyyy hh24:mi:ss'),
                       300
                       );

输出:

START_DATE                      UNITS
-------------------------- ----------
03-JUN-2014 14:00:00              100
03-JUN-2014 14:30:00              100
03-JUN-2014 15:00:00              100
04-JUN-2014 11:00:00       33.3333333
04-JUN-2014 11:30:00       33.3333333
04-JUN-2014 12:00:00       33.3333333
04-JUN-2014 12:30:00       33.3333333
04-JUN-2014 13:00:00       33.3333333
04-JUN-2014 13:30:00       33.3333333
04-JUN-2014 14:00:00       33.3333333
04-JUN-2014 14:30:00       33.3333333
04-JUN-2014 15:00:00       33.3333333

答案 2 :(得分:1)

此解决方案涵盖任何行数(其他答案仅涵盖一行或多行存在性能问题)。

这就是我的尝试:

with yourTable as (select 1 id,
                          to_date('06/03/2014 14:00:00','dd/mm/yyyy hh24:mi:ss') start_date,
                          to_date('06/03/2014 15:30:00','dd/mm/yyyy hh24:mi:ss') end_date,
                          300 units
                   from dual
                   union all
                   select 2 id,
                          to_date('05/03/2014 12:00:00','dd/mm/yyyy hh24:mi:ss') start_date,
                          to_date('06/03/2014 23:30:15','dd/mm/yyyy hh24:mi:ss') end_date,
                          1000 units
                   from dual),
     setting as (select 1/24/2 /*every half an hour*/ interval from dual),
     borders as (select /* +materialize */ min(start_date) - 1 mindate,max(end_date) + 1 maxdate from yourTable),
     periods as (select /* +materialize */
                        mindate + interval*level period_start,
                        mindate + interval*(level + 1) - 1/24/60/60 /* - 1 sec */ period_end
                 from borders,setting
                 connect by level <= (maxdate - mindate)/interval)
select id,
       units,
       units/count(*) over (partition by id)
from periods p, yourTable y
where y.start_date between p.period_start and p.period_end
   or p.period_start between y.start_date and y.end_date;

其中

  1. yourTable应该只用您的表格替换
  2. setting我用作设置期间的变量,使用1/24表示小时或1/24/4表示15分钟
  3. borders是一个变量,其中包含句点的最大和最小日期
  4. periods是所有需要时段的大部分内容
  5. 最后查询查找yourTable
  6. 中所有计算的期间和期间的所有交叉点

    对于您的示例,它返回4行,因为15:30是逻辑上的新时段(15:29:59将作为上一时段的结束)。但我认为您可以自己处理这个问题:它需要有关您的业务逻辑的其他信息。

    请注意,提示/* +materialize */非常重要,因为如果没有它,Oracle Analyzer就会喜欢用connect by level“打开”子查询,这会令人难以置信地表现不佳。