I have the following data, composed of the A
value, ordered by MM
(month).
The B
column is computed as GREATEST(current value of A + previous value of B, 0)
in a spreadsheet-like fashion.
How can I compute B
using a SQL Query?
I am using Oracle 10g, therefore I cannot use recursive queries.
Here is my test data:
MM | A | B
-----------+--------+------
2012-01-01 | 800 | 800
2012-02-01 | 1900 | 2700
2012-03-01 | 1750 | 4450
2012-04-01 | -20000 | 0
2012-05-01 | 900 | 900
2012-06-01 | 3900 | 4800
2012-07-01 | -2600 | 2200
2012-08-01 | -2600 | 0
2012-09-01 | 2100 | 2100
2012-10-01 | -2400 | 0
2012-11-01 | 1100 | 1100
2012-12-01 | 1300 | 2400
And here is the "table definition":
select t.* from (
select date'2012-01-01' as mm, 800 as a from dual union all
select date'2012-02-01' as mm, 1900 as a from dual union all
select date'2012-03-01' as mm, 1750 as a from dual union all
select date'2012-04-01' as mm, -20000 as a from dual union all
select date'2012-05-01' as mm, 900 as a from dual union all
select date'2012-06-01' as mm, 3900 as a from dual union all
select date'2012-07-01' as mm, -2600 as a from dual union all
select date'2012-08-01' as mm, -2600 as a from dual union all
select date'2012-09-01' as mm, 2100 as a from dual union all
select date'2012-10-01' as mm, -2400 as a from dual union all
select date'2012-11-01' as mm, 1100 as a from dual union all
select date'2012-12-01' as mm, 1300 as a from dual
) t;
To generate a running total in SQL, we can use something called a “window function”. A window function is a way to write a function so it looks over a range of records, or a “window”, instead of all records or a single record. In this case, we can use a window function to look at the SUM of the number of sales.
A recursive subquery factoring clause must contain two query blocks combined by a UNION ALL set operator. The first block is known as the anchor member, which can not reference the query name. It can be made up of one or more query blocks combined by the UNION ALL , UNION , INTERSECT or MINUS set operators.
Analytical functions are used to do 'analyze' data over multiple rows and return the result in the current row. E.g Analytical functions can be used to find out running totals, ranking the rows, do some aggregation on the previous or forthcoming row etc.
Analytic functions calculate an aggregate value based on a group of rows. Unlike aggregate functions, however, analytic functions can return multiple rows for each group. Use analytic functions to compute moving averages, running totals, percentages or top-N results within a group.
So let's unleash the MODEL
clause (a device whose mystery is only exceeded by its power) on this problem:
with data as (
select date'2012-01-01' as mm, 800 as a from dual union all
select date'2012-02-01' as mm, 1900 as a from dual union all
select date'2012-03-01' as mm, 1750 as a from dual union all
select date'2012-04-01' as mm, -20000 as a from dual union all
select date'2012-05-01' as mm, 900 as a from dual union all
select date'2012-06-01' as mm, 3900 as a from dual union all
select date'2012-07-01' as mm, -2600 as a from dual union all
select date'2012-08-01' as mm, -2600 as a from dual union all
select date'2012-09-01' as mm, 2100 as a from dual union all
select date'2012-10-01' as mm, -2400 as a from dual union all
select date'2012-11-01' as mm, 1100 as a from dual union all
select date'2012-12-01' as mm, 1300 as a from dual
)
select mm, a, b
from (
-- Add a dummy value for b, making it available to the MODEL clause
select mm, a, 0 b
from data
)
-- Generate a ROW_NUMBER() dimension, in order to access rows by RN
model dimension by (row_number() over (order by mm) rn)
-- Spreadsheet values / measures involved in calculations are mm, a, b
measures (mm, a, b)
-- A single rule will do. Any value of B should be calculated according to
-- GREATEST([previous value of B] + [current value of A], 0)
rules (
b[any] = greatest(nvl(b[cv(rn) - 1], 0) + a[cv(rn)], 0)
)
The above yields:
MM A B
01.01.2012 800 800
01.02.2012 1900 2700
01.03.2012 1750 4450
01.04.2012 -20000 0
01.05.2012 900 900
01.06.2012 3900 4800
01.07.2012 -2600 2200
01.08.2012 -2600 0
01.09.2012 2100 2100
01.10.2012 -2400 0
01.11.2012 1100 1100
01.12.2012 1300 2400
I came up with a user-defined aggregate function
create or replace type tsum1 as object
(
total number,
static function ODCIAggregateInitialize(nctx IN OUT tsum1 )
return number,
member function ODCIAggregateIterate(self IN OUT tsum1 ,
value IN number )
return number,
member function ODCIAggregateTerminate(self IN tsum1,
retVal OUT number,
flags IN number)
return number,
member function ODCIAggregateMerge(self IN OUT tsum1,
ctx2 IN tsum1)
return number
)
/
create or replace type body tsum1
is
static function ODCIAggregateInitialize(nctx IN OUT tsum1)
return number
is
begin
nctx := tsum1(0);
return ODCIConst.Success;
end;
member function ODCIAggregateIterate(self IN OUT tsum1,
value IN number )
return number
is
begin
self.total := self.total + value;
if (self.total < 0) then
self.total := 0;
end if;
return ODCIConst.Success;
end;
member function ODCIAggregateTerminate(self IN tsum1,
retVal OUT number,
flags IN number)
return number
is
begin
retVal := self.total;
return ODCIConst.Success;
end;
member function ODCIAggregateMerge(self IN OUT tsum1,
ctx2 IN tsum1)
return number
is
begin
self.total := self.total + ctx2.total;
return ODCIConst.Success;
end;
end;
/
CREATE OR REPLACE FUNCTION sum1(input number)
RETURN number
PARALLEL_ENABLE AGGREGATE USING tsum1;
/
Here is the query
with T1 as(
select date'2012-01-01' as mm, 800 as a from dual union all
select date'2012-02-01' as mm, 1900 as a from dual union all
select date'2012-03-01' as mm, 1750 as a from dual union all
select date'2012-04-01' as mm, -20000 as a from dual union all
select date'2012-05-01' as mm, 900 as a from dual union all
select date'2012-06-01' as mm, 3900 as a from dual union all
select date'2012-07-01' as mm, -2600 as a from dual union all
select date'2012-08-01' as mm, -2600 as a from dual union all
select date'2012-09-01' as mm, 2100 as a from dual union all
select date'2012-10-01' as mm, -2400 as a from dual union all
select date'2012-11-01' as mm, 1100 as a from dual union all
select date'2012-12-01' as mm, 1300 as a from dual
)
select mm
, a
, sum1(a) over(order by mm) as b
from t1
Mm a b
----------------------------
01.01.2012 800 800
01.02.2012 1900 2700
01.03.2012 1750 4450
01.04.2012 -20000 0
01.05.2012 900 900
01.06.2012 3900 4800
01.07.2012 -2600 2200
01.08.2012 -2600 0
01.09.2012 2100 2100
01.10.2012 -2400 0
01.11.2012 1100 1100
01.12.2012 1300 2400
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With