0

我在下表中有数据

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 所以期间2012 年 1 月 15 日至 2012 年 1 月 31 日 = 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 的情况下实现这一点

4

5 回答 5

1

you can use a LAG function to determine the daily average between one row and a previous (sorted) row.

once you have a daily average, you can multiply by the number of days in the period.

all in the same sql statement.

于 2012-04-25T12:33:38.130 回答
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'))
于 2012-04-25T14:00:27.460 回答
1

我看到 AB Cade 打败了我,但这是我的解决方案:

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>
于 2012-04-25T14:05:03.460 回答
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
于 2012-04-25T14:07:06.057 回答
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;

这段代码已经过测试,并给出了您想要的结果。

于 2012-04-25T14:08:05.720 回答