Reputation: 19
How to implement cumulative calculations on a column to calculate a value based on data in same column but previous row?
For instance: Considering below table and records
Data -
CREATE TABLE TEST ( DT DATE, NAME VARCHAR2(10), AMOUNT NUMBER(10,3));
insert into TEST values ( to_date( '01-jan-2021'), 'apple', 198.95 );
insert into TEST values ( to_date( '02-jan-2021'), 'apple', 6.15 );
insert into TEST values ( to_date( '03-jan-2021'), 'apple', 4.65 );
insert into TEST values ( to_date( '04-jan-2021'), 'apple', 20.85 );
insert into TEST values ( to_date( '01-jan-2021'), 'banana', 80.5 );
insert into TEST values ( to_date( '02-jan-2021'), 'banana', 9.5 );
insert into TEST values ( to_date( '03-jan-2021'), 'banana', 31.65 );
Desired Output:
DT NAME AMOUNT CALC
---------------------------------
1-Jan-21 apple 198.95 39.79
2-Jan-21 apple 6.15 33.062
3-Jan-21 apple 4.65 27.380
4-Jan-21 apple 20.85 26.074
.
.
.
1-Jan-21 banana 80.5 16.1
2-Jan-21 banana 9.5 14.78
3-Jan-21 banana 31.65 18.14
.
.
.
I need to write a query to obtain CALC for each record (grouped by NAME) based on below formula:
((CALC of prev day record * 4)+ AMOUNT of current record )/ 5
i.e for APPLE
for 1-jan-21, CALC = ((0*4)+198.95)/5 = 39.79 -------> since it is 1st record, have taken 0 as CALC of prev day record
for 2-jan-21, CALC = ((39.79*4)+6.15)/5= 33.062 -----> prev CALC is considered from 1-jan-21 - 39.79 and 6.15 from current row
for 3-jan-21, CALC = ((33.062*4)+4.65)/5= 27.380 and so on
For BANANA
1-jan-21, CALC = ((0*4)+80.5)/5=16.1
1-jan-21, CALC = ((16.1*4)+9.5)/5=14.78
etc
I have tried using lag, with, join etc and also looked upon internet but unable to arrive at a solution.
Thanks in advance!
Upvotes: 1
Views: 382
Reputation: 167972
If you have the successive rows with amounts: a1, a2, a3, ...
Then the calc
value for the successive rows is:
You can use this (and the fact that your data has successive days) to calculate the progression with the query:
SELECT t2.dt,
t2.name,
t2.amount,
SUM(POWER(4/5, t2.dt - t1.dt) * t1.amount / 5) AS calc
FROM test t1
INNER JOIN test t2
ON (t1.name = t2.name AND t1.dt <= t2.dt)
GROUP BY
t2.dt,
t2.name,
t2.amount
ORDER BY
t2.name,
t2.dt;
You could also use the MODEL
clause:
SELECT dt,
name,
amount,
calc
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY name ORDER BY dt) AS rn
FROM test t
)
MODEL
PARTITION BY (name)
DIMENSION BY (rn)
MEASURES ( dt, amount, 0 AS calc)
RULES (
calc[1] = amount[1]/5,
calc[rn>1] = (amount[cv(rn)] + 4*calc[cv(rn)-1])/5
)
Which, for your sample data, both output:
DT NAME AMOUNT CALC 01-JAN-21 apple 198.95 39.79 02-JAN-21 apple 6.15 33.062 03-JAN-21 apple 4.65 27.3796 04-JAN-21 apple 20.85 26.07368 01-JAN-21 banana 80.5 16.1 02-JAN-21 banana 9.5 14.78 03-JAN-21 banana 31.65 18.154
db<>fiddle here
Upvotes: 2