Oracle:按月分解多个条目

时间:2012-04-25 12:28:00

标签: sql oracle date-arithmetic

我的数据如下表所示

start_date      end_date        amount
15-01-2012      25-01-2012      100
26-01-2012      10-02-2012      100
11-02-2012      29-02-2012      100

我希望这些条目按月分解

  • 15-01至25-01 = 100
  • 26-01至31-01 = 100 /((10-02-2012 - 26-01-2012)+1)*((31-01-2012 - 26-01-2012)+ 1)= 37.5 因此,对于15-01-2012至31-01-2012期间= 100 + 37.5 = 137.5

2月份的金额应为

  • 01-02至10-02 = 100 /((10-02-2012 - 26-01-2012)+1)*((01-02-2012 - 10-02-2012)+ 1)= 62.5
  • 11-02至29-02 = 100

所以2月01-02-2012至29-02-2012 = 62.5 + 100 = 162.5

以便最终输出

start_date      end_date        amount
15-01-2012   31-01-2012                  137.5
01-02-2012   29-02-2012          162.5

是否有任何方法可以在不使用PLSQL的情况下实现此目的

5 个答案:

答案 0 :(得分:1)

您可以使用LAG函数来确定一行与上一行(已排序)之间的每日平均值。

如果您有每日平均值,则可以乘以该期间的天数。

所有在同一个sql语句中。

答案 1 :(得分:1)

我不确定你想如何计算这些值,但作为一个开始,尝试每月打破记录:

with dts as (select last_day(add_months(
  to_date('20111201','yyyymmdd'),level)) ld,
             add_months(
  to_date('20111201','yyyymmdd'),level) sd
             from dual connect by level < 12)
select case when start_date >= sd then start_date  else sd end st_dt,
case when end_date <= ld then end_date  else ld end en_dt, amount,
ld-sd days_in_month,
case when end_date <= ld then end_date  else ld end-
case when start_date >= sd then start_date  else sd end part
from t, dts
where (start_date >= sd and to_char(start_date, 'yyyymm') = 
       to_char(sd, 'yyyymm'))
       or (end_date <= ld and to_char(end_date, 'yyyymm') = 
       to_char(ld, 'yyyymm'))

答案 2 :(得分:1)

我看到A.B.凯德打败了我,但这是我的解决方案:

SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MM-YYYY'
  2  /

Session altered.

SQL> CREATE TABLE t (start_date DATE, end_date DATE, amount NUMBER);

Table created.

SQL> INSERT INTO t VALUES (TO_DATE('20120115','YYYYMMDD'),TO_DATE('20120125','YYYYMMDD'),100);

1 row created.

SQL> INSERT INTO t VALUES (TO_DATE('20120126','YYYYMMDD'),TO_DATE('20120210','YYYYMMDD'),100);

1 row created.

SQL> INSERT INTO t VALUES (TO_DATE('20120211','YYYYMMDD'),TO_DATE('20120229','YYYYMMDD'),100);

1 row created.

SQL>

设置了一些测试数据......

SQL> COL for_month       FOR A9
SQL> COL pro_rated_start FOR A15
SQL> COL pro_rated_end   FOR A13
SQL>

...并格式化了一些列......

SQL> WITH months AS (
  2      SELECT TRUNC(MIN(start_date),'MM') min_start_month
  3      ,      MONTHS_BETWEEN(TRUNC(MAX(end_date),'MM'),TRUNC(MIN(start_date),'MM')) + 1 mos
  4      FROM   t
  5  )
  6  , offset AS (
  7      SELECT ROWNUM - 1 r
  8      FROM  (SELECT NULL
  9             FROM   DUAL
 10             CONNECT BY LEVEL <= (SELECT mos FROM months))
 11  )
 12  , ranges AS (
 13      SELECT  ADD_MONTHS(months.min_start_month, offset.r)                mo_start
 14      ,       LAST_DAY(ADD_MONTHS(months.min_start_month, offset.r))      mo_end
 15      FROM    offset
 16      ,       months
 17  )
 18  SELECT      TO_CHAR(GREATEST(t.start_date,ranges.mo_start),'Mon YYYY') for_month
 19  ,           MIN(GREATEST(t.start_date,ranges.mo_start))                pro_rated_start
 20  ,           MAX(LEAST(t.end_date,ranges.mo_end))                       pro_rated_end
 21  ,           SUM(t.amount
 22                  * CASE
 23                    WHEN t.end_date < ranges.mo_end
 24                     AND t.start_date > ranges.mo_start
 25                    THEN 1
 26                    ELSE ((LEAST(t.end_date,ranges.mo_end)
 27                        - GREATEST(t.start_date,ranges.mo_start) + 1)
 28                       / (t.end_date - t.start_date + 1))
 29                    END)  pro_rated_amount
 30  FROM        t
 31  ,           ranges
 32  WHERE       t.start_date <= ranges.mo_end
 33  AND         t.end_date >= ranges.mo_start
 34  GROUP BY    TO_CHAR(GREATEST(t.start_date,ranges.mo_start),'Mon YYYY');

FOR_MONTH PRO_RATED_START PRO_RATED_END PRO_RATED_AMOUNT
--------- --------------- ------------- ----------------
Jan 2012  15-01-2012      31-01-2012               137.5
Feb 2012  01-02-2012      29-02-2012               162.5

SQL>

答案 3 :(得分:1)

SQL> select * from q10315606;

START_DATE END_DATE       AMOUNT
---------- ---------- ----------
2012-01-15 2012-01-25        100
2012-01-26 2012-02-10        100
2012-02-11 2012-02-29        100

SQL> with day_generator as
  2  -- this block expands out every date in the entire date range
  3  (
  4  select min_start_date + level - 1 as date_value
  5    from (select min(start_date) as min_start_date,
  6                 max(end_date) - min(start_date) + 1 as total_days
  7            from q10315606
  8         )
  9   connect by level <= total_days
 10  ),
 11  range_averages as
 12  -- this block generates a daily average for each date range
 13  (
 14  select start_date, end_date,
 15         end_date - start_date + 1 as days_inclusive,
 16         amount / (end_date - start_date + 1)  as daily_amount
 17    from q10315606
 18  )
 19  select start_date, end_date, sum(amount) as amount
 20    from (
 21  -- here we cast all the dates to the minimum and maximum value found in the month
 22          select min(dg.date_value)
 23                   over (partition by last_day(dg.date_value) ) as start_date,
 24                 max(dg.date_value)
 25                   over (partition by last_day(dg.date_value) ) as end_date,
 26                 ra.daily_amount as amount
 27            from range_averages ra,
 28                 day_generator dg
 29           where dg.date_value between ra.start_date and ra.end_date
 30         )
 31   group by start_date, end_date
 32*  order by start_date, end_date
amusch@AGDEV:SQL> /

START_DATE END_DATE       AMOUNT
---------- ---------- ----------
2012-01-15 2012-01-31      137.5
2012-02-01 2012-02-29      162.5

答案 4 :(得分:1)

这是一次尝试:

我加入了几个月。如果一个行在两个月内将被“重复”,更确切地说将在两个月分发。 对于每行*月,我应用公式amount/(days in current row) * (days in current month)

with months as 
    (select add_months('1-Jan-2012', level -1) as mon
    from dual 
    connect by level < 12)
select mon, sum(amount)
from(
    select 
        months.mon, 
        amount/(end_date - start_date +1)*
           (case when trunc(start_date,'MM') = months.mon 
                 then least(last_day(start_date), end_date) - start_date +1 
                 else end_date - greatest(months.mon, start_date) +1 
            end) as amount
    from your_table y 
    join months on 
      (months.mon between trunc(start_date,'MM') and trunc(end_date, 'MM'))
)
group by mon;

此代码已经过测试,可以准确显示您想要的结果。