Derive function from table in PLSQL

Viewed 106

I have created a demo function to add two numbers in PLSQL:

create or replace FUNCTION Addition(    
    x number,
    y number
    )     
    RETURN number
AS
    result number; 
BEGIN
    result := x+y;  
    RETURN result;  
END; 
/
commit;

Now, I have following table:

Seq  Inputl  lnput2  Function
---  ------  ------  ----------------
A         1       2  Addition
B         3       4  Subtraction
C         5       6  Addition
D         7       8  Addition
E         9      10  Addition

I have to use functions in my procedure as mentioned below:

select * bulk collect into data from table order by Seq asc;

for i in 1 .. data.count loop
   result(i) := data(i).function(data(i).Input1,data(i).Input2);
end loop;

I am getting error by using this way.

Is there any other way to achieve this?

4 Answers

You could create a function that accepts an operation name from your table such as 'Addition' or 'Subtraction':

create or replace function do_the_arith
    ( operation varchar2
    , x number
    , y number )
    return number
as
begin
    return
        case upper(operation)
            when 'ADDITION' then x + y
            when 'MULTIPLICATION' then x * y
            when 'SUBTRACTION' then x - y
            when 'DIVISION' then x / nullif(y,0)  -- to avoid zero divide
            when 'POWER' then power(y,y)
            when 'LOG' then log(x,y)
        end;
end do_the_arith;
/

with t (seq, input1, input2, operation) as
     ( select 'A', 1,  2, 'Addition' from dual union all
       select 'B', 3,  4, 'Multiplication' from dual union all
       select 'C', 5,  6, 'Subtraction' from dual union all
       select 'D', 7,  8, 'Power' from dual union all
       select 'E', 9, 10, 'Log' from dual )
select t.*
     , do_the_arith(operation, input1, input2) as result
from   t;

SEQ     INPUT1     INPUT2 OPERATION          RESULT
--- ---------- ---------- -------------- ----------
A            1          2 Addition                3
B            3          4 Multiplication         12
C            5          6 Subtraction            -1
D            7          8 Power            16777216
E            9         10 Log            1.04795163

Below is an object-relational solution to your question. The final PL/SQL looks almost identical to your code snippets:

declare
    type data_nt is table of table1%rowtype;
    data data_nt;
    result number;
begin
    select * bulk collect into data from table1 order by seq asc;

    for i in 1 .. data.count loop
        result := data(i).operation.operate(data(i).input1, data(i).input2);
        dbms_output.put_line('Result: '||result);
    end loop;
end;
/

DBMS_OUTPUT:
Result: 2
Result: 0

Below are the steps to create the supporting objects. First, we need to define a superclass. This superclass doesn't do anything, it's just a container to allow the subclasses to be stored together.

--Rollback:
-- drop table operations;
-- drop type addition;
-- drop type operation;

create or replace type operation is object
(
    --Weird PL/SQL limitation - every type must have a value, so add a dummy column.
    dummy varchar2(1),
    member function operate(operand1 number, operand2 number) return number,
    --Custom constructor so we don't have to pass it a dummy value.
    constructor function operation return self as result
)
not final;
/

create or replace type body operation is
    member function operate(operand1 number, operand2 number) return number is
    begin
        return null;
    end;

    constructor function operation return self as result is begin return; end;
end;
/

Next, we create subclasses that actually implement the math functions.

create or replace type addition under operation
(
    overriding member function operate(operand1 number, operand2 number) return number,
    constructor function addition return self as result
) not final;
/

create or replace type body addition is
    overriding member function operate(operand1 number, operand2 number) return number is
    begin
        return operand1 + operand2;
    end;

    constructor function addition return self as result is begin return; end;
end;
/

create or replace type subtraction under operation
(
    overriding member function operate(operand1 number, operand2 number) return number,
    constructor function subtraction return self as result
) not final;
/

create or replace type body subtraction is
    overriding member function operate(operand1 number, operand2 number) return number is
    begin
        return operand1 - operand2;
    end;

    constructor function subtraction return self as result is begin return; end;
end;
/

Finally, we create a table that can hold an OPERATION type, along with some other data.

create table table1
(
    seq varchar2(10) primary key,
    input1 number,
    input2 number,
    operation operation
);

insert into table1 values('A', 1, 1, addition());
insert into table1 values('B', 1, 1, subtraction());
commit;

Downsides

This approach requires a lot of code - 1500 characters to implement "+" and "-". All your SQL statements will become complicated, and people will have difficulty reading or writing the data. Many tools won't support object-relational data. Mixing code and data causes some problems - you can't modify the type specifications without generating an error like "ORA-02303: cannot drop or replace a type with type or table dependents". (And do NOT just jump to using the "FORCE" option. If you force the types to compile, you will break the table and lose your data.) Performance will probably be lousy.

I've never worked with a system like this and not hated it. It's usually better to build a "dirty" dynamic code solution than a "pure" object-relational solution. But if you're careful and document everything, there might be a good use for this kind of system.

Looks like dynamic SQL.

However, as there are only two functions you'd like to use (and - if number of these stays that low), simpler option is to use CASE:

(Pseudocode)

result := case when function = 'Addition'    then addition   (input1, input2)
               when function = 'Subtraction' then subtraction(input1, input2)
          end;

Agree to the solution by William Robertson, However for DIVISION and LOG function you need to handle the exception ORA-01476: divisor is equal to zero and ORA-01428: argument '0' is out of range respectively.

For DIVISION we can just modify the divisor,

CASE upper(&operation) WHEN 'DIV' THEN
              x / CASE y WHEN 0 THEN 1 ELSE y END

For LOG you have to check what you want to do if the validation fails. Check the validation here

Related