This version also accepts precision as a parameter:
with q as (select 100 as val, 3 as parts, 2 as prec from dual) select rownum as no ,case when rownum = parts then val - round(val / parts, prec) * (parts - 1) else round(val / parts, prec) end v from q connect by level <= parts no v === ===== 1 33.33 2 33.33 3 33.34
For example, if you want to divide the value between the number of days in the current month, you can do this:
with q as (select 100 as val ,extract(day from last_day(sysdate) as parts ,2 as prec from dual) select rownum as no ,case when rownum = parts then val - round(val / parts, prec) * (parts - 1) else round(val / parts, prec) end v from q connect by level <= parts; 1 3.33 2 3.33 3 3.33 4 3.33 ... 27 3.33 28 3.33 29 3.33 30 3.43
To distribute the value between each month, weighted by the number of days in each month, you can do this instead (change level <= 3 to change the number of months in which it is calculated):
with q as ( select add_months(date '2013-07-01', rownum-1) the_month ,extract(day from last_day(add_months(date '2013-07-01', rownum-1))) as days_in_month ,100 as val ,2 as prec from dual connect by level <= 3) ,q2 as ( select the_month, val, prec ,round(val * days_in_month / sum(days_in_month) over (), prec) as apportioned ,row_number() over (order by the_month desc) as reverse_rn from q) select the_month ,case when reverse_rn = 1 then val - sum(apportioned) over (order by the_month rows between unbounded preceding and 1 preceding) else apportioned end as portion from q2; 01/JUL/13 33.7 01/AUG/13 33.7 01/SEP/13 32.6
Jeffrey kemp
source share