Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Oracle's user-defined aggregation function be defined for use with two columns?

Tags:

oracle

I'd like to implement a custom regression aggregate function, that is similar to the existing REGR_SLOPE.

The function I want to define needs to get two columns as a parameter, e.g.

select 
   T.EMPLOYEE_ID,
   CUSTOM_REGR_SLOPE(T.DATE, T.SALARY) as SALARY_TREND
from (...) T
group by T.EMPLOYEE_ID;

The Oracle's documentation suggests it may not be possible, but I may be bad at reading between the lines ;-) .

We use Oracle 12.

like image 790
Adam Ryczkowski Avatar asked Dec 25 '22 17:12

Adam Ryczkowski


1 Answers

Yes, its possible if you really want/need to. You can do something like this:

First, create an object type:

create or replace type two_nums_t as object
(
num1 number,
num2 number
);

Then create your custom spec:

CREATE OR REPLACE TYPE TotalSumPair
AS OBJECT (
runningSum1 number,
runningCnt1 number,
runningSum2 number,
runningCnt2 number,

STATIC FUNCTION ODCIAggregateInitialize
  ( actx IN OUT TotalSumPair
  ) RETURN NUMBER,

MEMBER FUNCTION ODCIAggregateIterate
  ( self  IN OUT TotalSumPair,
    val   IN     two_nums_t
  ) RETURN NUMBER,

MEMBER FUNCTION ODCIAggregateTerminate
  ( self             IN   TotalSumPair,
    returnValue  OUT  NUMBER, -- return 
    flags           IN   NUMBER
  ) RETURN NUMBER,

MEMBER FUNCTION ODCIAggregateMerge
  (self  IN OUT TotalSumPair,
   ctx2 IN      TotalSumPair
  ) RETURN NUMBER
);

And custom body:

CREATE OR REPLACE TYPE BODY TotalSumPair AS
STATIC FUNCTION ODCIAggregateInitialize
  ( actx IN OUT TotalSumPair
  ) RETURN NUMBER IS 
  BEGIN
    IF actx IS NULL THEN
      actx := TotalSumPair(0,0,0,0);
    ELSE
      actx.runningSum1 := 0;
      actx.runningCnt1 := 0;
      actx.runningSum2 := 0;
      actx.runningCnt2 := 0;
    END IF;
    RETURN ODCIConst.Success;
  END;

MEMBER FUNCTION ODCIAggregateIterate
  ( self  IN OUT TotalSumPair,
    val   IN     two_nums_t
  ) RETURN NUMBER IS
  BEGIN
    self.runningSum1 := self.runningSum1 + nvl(val.num1,0);
    self.runningSum2 := self.runningSum2 + nvl(val.num2,0);
    self.runningCnt1 := self.runningCnt1 + 1;
    self.runningCnt2 := self.runningCnt2 + 1;
    RETURN ODCIConst.Success;
  END;

MEMBER FUNCTION ODCIAggregateTerminate
  ( self        IN  TotalSumPair,
    ReturnValue OUT NUMBER,
    flags       IN  NUMBER
  ) RETURN NUMBER IS
  BEGIN
    --if (runningCnt1 <> 0) then
        returnValue := (self.runningSum1 + self.runningSum2);
    --else
    --    returnValue := self.runningSum1;
    --end if;
    RETURN ODCIConst.Success;
  END;

MEMBER FUNCTION ODCIAggregateMerge
  (self IN OUT TotalSumPair,
   ctx2 IN     TotalSumPair
  ) RETURN NUMBER IS
  BEGIN
    self.runningSum1 := self.runningSum1 + ctx2.runningSum1;
    self.runningCnt1 := self.runningCnt1 + ctx2.runningCnt1;
    self.runningSum2 := self.runningSum2 + ctx2.runningSum2;
    self.runningCnt2 := self.runningCnt2 + ctx2.runningCnt2;
    RETURN ODCIConst.Success;
  END;

END;

Define your function:

CREATE OR REPLACE FUNCTION total_sum_pair( x two_nums_t) 
RETURN number  PARALLEL_ENABLE
AGGREGATE USING TotalSumPair;

Now call it like so:

with x as (
 select 'X' as id, 1 as num1, 2 as num2 from dual
 union all
 select 'X' as id, 3 as num1, 4 as num2 from dual
 union all
 select 'Z' as id, 5 as num1, 6 as num2 from dual
)
select id, total_sum_pair(two_nums_t(num1, num2)) sum
from x
group by id;

Output:

ID  SUM
X   10
Z   11

This sums both numbers for each X rows (1+2+3+4), and each Y rows (5+6).

Phew! ;)

like image 69
tbone Avatar answered May 13 '23 18:05

tbone