Custom aggregate function inside a package - sql

I'm trying to write a custom aggregate function in Oracle and group that function inside a package together with some other functions that I have. As an example (to simulate the problem I have) suppose my custom aggregation to do a summation of numbers looks like:
CREATE OR REPLACE TYPE SUM_AGGREGATOR_TYPE AS OBJECT (
summation NUMBER,
STATIC FUNCTION ODCIAggregateInitialize(agg_context IN OUT
SUM_AGGREGATOR_TYPE) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(self IN OUT SUM_AGGREGATOR_TYPE,
next_number IN NUMBER) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(self IN OUT SUM_AGGREGATOR_TYPE,
para_context IN SUM_AGGREGATOR_TYPE) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(self IN SUM_AGGREGATOR_TYPE,
return_value OUT NUMBER, flags IN NUMBER) RETURN NUMBER
);
CREATE OR REPLACE TYPE BODY SUM_AGGREGATOR_TYPE IS
STATIC FUNCTION ODCIAggregateInitialize(agg_context IN OUT
SUM_AGGREGATOR_TYPE)
RETURN NUMBER IS
BEGIN
agg_context := SUM_AGGREGATOR_TYPE(NULL);
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate(self IN OUT SUM_AGGREGATOR_TYPE,
next_number IN NUMBER)
RETURN NUMBER IS
BEGIN
IF self.summation IS NULL THEN
self.summation := next_number;
ELSIF summation IS NOT NULL THEN
self.summation := self.summation + next_number;
END IF;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge(self IN OUT SUM_AGGREGATOR_TYPE,
para_context IN SUM_AGGREGATOR_TYPE)
RETURN NUMBER IS
BEGIN
self.summation := self.summation + para_context.summation;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate(self IN SUM_AGGREGATOR_TYPE,
return_value OUT NUMBER, flags IN NUMBER)
RETURN NUMBER IS
BEGIN
return_value := self.summation;
return ODCIConst.Success;
END;
END;
If I write the following function definition:
CREATE OR REPLACE FUNCTION MY_SUM(input NUMBER)
RETURN NUMBER PARALLEL_ENABLE AGGREGATE USING SUM_AGGREGATOR_TYPE;
and corresponding type declaration to test:
CREATE OR REPLACE TYPE VECTOR
IS
TABLE OF NUMBER;
this statement:
select my_sum(column_value) from table(vector(1, 2, 1, 45, 22, -1));
gives the correct result of 70. However, creating a package with the function definition:
CREATE OR REPLACE PACKAGE MY_FUNCTIONS AS
FUNCTION MY_SUM(input NUMBER)
RETURN NUMBER PARALLEL_ENABLE AGGREGATE USING SUM_AGGREGATOR_TYPE;
END;
and calling it via:
select MY_FUNCTIONS.my_sum(column_value) from table(vector(1, 2, 1, 45, 22, -1));
explodes with
ORA-00600: internal error code, arguments: [17090], [], [], [], [], [], [], [], [], [], [], []
Is it possible to have custom aggregate functions nested inside package declarations?

Oracle uses ORA-00600 to signal unhandled exceptions i.e. bugs. The first argument indicates the exception; ORA-17090 is a generic "operation not allowed". Frequently they are restricted to specific permutations of database version and OS platform. Other times it just means we're doing something really unusual.
Does including a custom aggregate function inside a package count as "really unusual"? Not sure. Certainly we are permitted to include data cartridge functions in PL/SQL functions. But user-defined aggregates are a special case of ODCI. While the documentation has no explicit rule against packages all the examples implement the aggregate using CREATE FUNCTION.
So, what to do? Well, ORA-00600 messages require the intervention of Oracle Support, as it needs a patch. If you have a Support account you can find out more about this particular issue here. You will need to raise an iTAR to get further resolution. Otherwise I'm afraid you're probably out of luck.

Related

How to correct '' PLS-00306: wrong number or types of arguments in call to ''

I want to call a function(ADD_PRICELIST) that gets values in this one:
*the add_pricelist function complied successfully.
CREATE OR REPLACE FUNCTION PRICELIST_DATA(
CPRIC_DATA_ID CUSTOMERS.CUSTOMERS_ID%TYPE,
IPRIC_DATA_ID ITEMS.ITEMS_ID%TYPE
)
RETURN NUMBER AS
L_PRICELIST_DATA_ID NUMBER;
BEGIN
L_PRICELIST_DATA_ID := ADD_PRICELIST(ROUND(dbms_random.value(0,10),3));
END IF;
RETURN L_PRICELIST_DATA_ID;
END PRICELIST_DATA;
Gives me:
PLS-00306: wrong number or types of arguments in call to ADD_PRICELIST
Your ADD_PRICELIST function as the signature:
CREATE OR REPLACE FUNCTION ADD_PRICELIST(
L_PLIST_CUST_ID CUSTOMERS.CUSTOMERS_ID%TYPE,
L_PLIST_ITEMS_ID ITEMS.ITEMS_ID%TYPE,
P_PRICELIST_PRICE NUMBER
) RETURN NUMBER
This takes 3 values and returns 1 value (which, as an aside, will always return NULL).
You are only calling it with a single argument:
L_PRICELIST_DATA_ID := MINI_ERP.ADD_PRICELIST(
ROUND(dbms_random.value(0,10),3)
);
Which gives you the error:
PLS-00306: wrong number or types of arguments in call to ADD_PRICELIST
To solve this, you need to pass the other 2 arguments so that the function has all 3 expected arguments.
For example:
L_PRICELIST_DATA_ID := MINI_ERP.ADD_PRICELIST(
ROUND(dbms_random.value(0,10),3),
ROUND(dbms_random.value(0,10),3),
ROUND(dbms_random.value(0,10),3)
);

How to compare two oracle sql object type with common base super class

Example:
Figure_t base class (super class)
sphere_t under figure_t
pyramid_t under figure_t
both has volume.
How to do objects comparison using map or order function?
What I am doing is using the map member function in the super class for comparing using the volume. I have tried with/without override of the map function in the subclass but still no luck. I can compare if I create the same object twice but not if I create different ones.
In example below I paste just the sphere since it is almost the same for both sphere and pyramid.
This is my super class:
CREATE OR REPLACE TYPE figure_t AS OBJECT (
v_volume NUMBER,
v_area NUMBER,
MAP MEMBER FUNCTION compare RETURN NUMBER, PRAGMA restrict_references ( compare, wnds, trust )
);
/
CREATE OR REPLACE TYPE BODY figure_t AS
MAP MEMBER FUNCTION compare RETURN NUMBER IS
BEGIN
RETURN v_volume;
END;
END;
/
ALTER TYPE figure_t NOT FINAL
CASCADE;
/
Then, this is my subtype:
CREATE OR REPLACE TYPE sphere_t UNDER figure_t (
v_radio NUMBER,
CONSTRUCTOR FUNCTION sphere_t (
radio NUMBER
) RETURN SELF AS RESULT,
MEMBER FUNCTION get_volume RETURN NUMBER,
MEMBER FUNCTION get_area RETURN NUMBER,
OVERRIDING MAP MEMBER FUNCTION compare RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY sphere_t AS
CONSTRUCTOR FUNCTION sphere_t (
radio NUMBER
) RETURN SELF AS RESULT IS
BEGIN
self.v_radio := radio;
self.v_volume := ( 4 / 3 ) * 3.141592654 * power(radio, 3);
self.v_area := 4 * 3.141592654 * power(radio, 2);
return;
END;
MEMBER FUNCTION get_volume RETURN NUMBER IS
BEGIN
RETURN v_volume;
END;
MEMBER FUNCTION get_area RETURN NUMBER IS
BEGIN
RETURN v_area;
END;
OVERRIDING
MAP MEMBER FUNCTION compare RETURN NUMBER IS
BEGIN
RETURN self.v_volume;
END;
END;
/
For doing the comparison it looks like:
DECLARE
sphere_v sphere_t;
pyramid_v pyramid_t;
BEGIN
pyramid_v := pyramid_t(120, 90, 30);
sphere_v := sphere_t(10);
IF ( sphere_v != pyramid_v ) THEN
dbms_output.put_line('NOT EQUAL');
END IF;
END;
There should be a way for this comparison since figures have a super class in common.
There should be a way for this comparison since figures have a super
class in common
Am not very sure as what you wanted to achieve here. Also what is definition of pyramid_t which you are using in your comparison.
IF ( sphere_v != pyramid_v ) THEN
The above condition looks dicey to me as this will always be the case.
When you do sphere_v := sphere_t(10); means you try to get all the return of the sphere_t to sphere_v.
So it would be good if you could compare the volume and area of the Sphere and pyramid separately. See below demo how you could take these value:
DECLARE
sphere_v sphere_t;
-- pyramid_v pyramid_t;
BEGIN
sphere_v := sphere_t(10);
dbms_output.put_line('Input Radio -->'||sphere_v.v_radio);
dbms_output.put_line('Volume of Sphere-->'||sphere_v.v_volume);
dbms_output.put_line('Area Of Sphere -->'||sphere_v.v_area);
--Similarly you can take the values of `volume` and `area`
--of pyramid and get it compared with that of Sphere.
-- pyramid_v := pyramid_t(120, 90, 30);
-- dbms_output.put_line('Input Radio Pyramid -->'||pyramid_v.v_radio);
-- dbms_output.put_line('Volume of Pyramid -->'||pyramid_v.v_volume);
-- dbms_output.put_line('Area Of Pyramid -->'||pyramid_v.v_area);
-- If sphere_v.v_volume = pyramid_v.v_volume then
-- dbms_output.put_line('Equal');
-- Else
-- dbms_output.put_line('Not Equal');
END;
Assumption: pyramid_t also have the same Object Body definition having volume and area calculation.
There should be a way for this comparison since figures have a super class in common.
There is a way, it's just not obvious.
When the types are exactly the same e.g. two instances of the same subtype we can invoke the map function implicitly. So we can compare two spheres like this:
IF ( sphere_1 != sphere_2 ) THEN ...
However, to compare two different subtypes we need to invoke the parent map function, and to make this happen we must reference it explicitly:
IF ( sphere_v.compare() != pyramid_v.compare() ) THEN ...
Yes, this is clunky. But Oracle is an RDBMS not an ORDBMS (whatever they claimed back in the version 8.0 days).

How to get quantitative and qualitative info about function arguments in PostgreSQL?

I have the following function:
func_name(p_1, p_2, p_3, ...)
Is there any way in the body of the function to query and get, for example:
a) the total number of the arguments/parameters of the current function
b) the type of them
Of course, I could modify the function as
func_name(n, ..., p_1, p_2, p_3, ...)
and include the info I require as arguments but it does not make me happy!
I would prefer to create a table with all functions' info (function name, function argument names and types) and query that when I have to.
Tia
you can try parsing it. lets fn() be:
t=# create or replace function fn(i int, t text) returns void as $$
declare
c text;
a text[];
begin
GET DIAGNOSTICS c = PG_CONTEXT;
raise info 'name: %',substring(c from 'function (.*?)\(');
a := string_to_array(substring(c from '\((.*?)\)'), ',');
raise info 'args: %',a;
raise info 'names: %', (select proargnames from pg_proc where proname = substring(c from 'function (.*?)\('));
raise info 'amount: %',array_length(a,1);
end;
$$ language plpgsql;
CREATE FUNCTION
so the info is:
t=# select fn(1,null);
INFO: name: fn
INFO: args: {integer,text}
INFO: names: {i,t}
INFO: amount: 2
fn
----
(1 row)
of course it will be more complicated to support not unique function name select and so on. This is just a simple example

How to call a sql function?

I have written a function minimum2 which take two numbers as arguments and returns the minimum number. This function compiles without any error. when I call the function minimum(1,2); I get the errors PLS-00103. Here is the function and the function call:
CREATE OR REPLACE FUNCTION minimum2(v1 number, v2 number) RETURN number IS
BEGIN
IF v1 < v2 THEN
RETURN v1;
ELSE
RETURN v2;
END IF;
END;
--I call the function asl follow
minimum2(1,2);
What did I do wrong? I wrote this code in sql developer
You need to run a select
select minimum2(1,2)
from dual
You also need to end the function with a /:
For details on how and why to use the / see here
Are you aware that there is a built-in function for that?
select least(1,2)
from dual
--specifying 'in' and 'out' to parameters is important or it can work as it is, but best practice is to use:
CREATE OR REPLACE FUNCTION minimum2(v1 IN number, v2 IN number) RETURN number IS
BEGIN
IF v1 < v2 THEN
RETURN v1;
ELSE
RETURN v2;
END IF;
END;
--I call the function as follows through anonymous block and you can not call the function directly.
set serveroutput on;
begin
dbms_output.put_line('Output is ' || minimum2(1,2));
END;
/

Adding Many (UDFs) Validation Functions to Oracle - Which Method Run Fastest

I have to move around 50+ validation functions into Oracle. I'm looking for the approach that runs fastest, but also would like to get around a boolean issue if possible. The return object for them all needs to be the same so that the application can react off the result in a consistent fashion and alert the user or display whatever popups, messages we may need. I created a valObj for this, but not sure yet if that is the best approach. The return format can be changed because the front-end that reacts off of it is not developed yet. In the end it will contain many different validation functions, from integer, number, phone, email, IPv4, IPv6, etc... This is what I have so far...
/***
This is the validation object.
It stores 1 for valid, 0 for not valid and some helper text that can be relayed back to the user.
***/
create or replace type valObj as object (
result number(1),
resultText varchar(32000)
);
/***
Coming from ColdFusion this seems clean to me but the function
will end up being a couple thousand lines long.
***/
create or replace function isValid(v in varchar2, format in varchar2)
return valObj
is
test number;
begin
if format = 'number' then
begin
test := to_number(v);
return valObj(1,null);
exception when VALUE_ERROR then return valObj(0,'Invalid number. Valid formats are: 12345, 12345.67, -12345, etc...');
end;
elsif format = 'integer' then
null; --TO DO
elsif format = 'email' then
null; --TO DO
elsif format = 'IPv4' then
null; --TO DO
elsif format = 'IPv6' then
null; --TO DO
end if;
--dozens of others to follow....
end;
/
/* Example Usage in SQL */
select isValid('blah','number') from dual; -- returns: (0, Invalid number. Valid formats are: 12345, 12345.67, -12345, etc...)
select isValid('blah','number').result from dual; -- returns: 0
select isValid('blah','number').resulttext from dual; -- returns: Valid formats are: 12345, 12345.67, -12345, etc...
select isValid(1234567890.123,'number') from dual; -- returns: 1,{null}
select isValid(1234567890.123,'number').result from dual; -- returns: 1
select isValid(1234567890.123,'number').resulttext from dual; -- returns: {null}
/* Example Usage in PL/SQL */
declare
temp valObj;
begin
temp := isValid('blah','number');
if (temp.result = 0) then
dbms_output.put_line(temp.resulttext);
else
dbms_output.put_line('Valid');
end if;
end;
/
My questions are:
When using it in PL/SQL I would love to be able to do boolean checks instead like this: if (temp.result) then but I can't figure out a way, cause that won't work in SQL. Should I just add a 3rd boolean attribute to the valObj or is there another way I don't know of?
These validation functions could end up being called within large loops. Knowing that, is this the most efficient way to accomplish these validations?
I'd appreciate any help. Thanks!
UPDATE: I forgot about MEMBER FUNCTIONS. Thanks #Brian McGinity for reminding me. So I'd like to go with this method since it keeps the type and its functions encapsulated together. Would there be any speed difference between this method and a stand-alone function? Would this be compiled and stored the same as a stand-alone function?
create or replace type isValid as object (
result number(1),
resulttext varchar2(32000),
constructor function isValid(v varchar, format varchar) return self as result );
/
create or replace type body isValid as
constructor function isValid(v varchar, format varchar) return self as result as
test number;
begin
if format = 'number' then
begin
test := to_number(v);
self.result := 1;
self.resulttext := null;
return;
exception when VALUE_ERROR then
self.result := 0;
self.resulttext := 'Invalid number. Valid formats are: 12345, 12345.67, -12345, etc...';
return;
end;
elsif format = 'phone' then
null; --TO DO
end if;
--and many others...
end;
end;
/
/* Example Usage in SQL */
select isValid('a','number') from dual;
/* Example Usage in PL/SQL */
declare
begin
if (isValid('a','number').result = 1) then
null;
end if;
end;
/
TEST RESULTS:
/* Test isValid (the object member function), this took 7 seconds to run */
declare
begin
for i in 1 .. 2000000 loop
if (isValid('blah','number').result = 1) then
null;
end if;
end loop;
end;
/* Test isValid2 (the stand-alone function), this took 16 seconds to run */
declare
begin
for i in 1 .. 2000000 loop
if (isValid2('blah','number').result = 1) then
null;
end if;
end loop;
end;
Both isValid and isValid2 do the same exact code, they just run this line test := to_number(v); then do the exception if it fails and return the result. Does this appear to be a valid test? The Object member function method is actually faster than a stand-alone function???
The stand-alone function can be much faster if you set it to DETERMINISTIC and if the data is highly repetitive. On my machine this setting decreased run time from 9 seconds to 0.1 seconds. For reasons I don't understand that setting does not improve performance of the object function.
create or replace function isValid2(v in varchar2, format in varchar2)
return valObj
deterministic --<< Hit the turbo button!
is
test number;
begin
if format = 'number' then
begin
test := to_number(v);
return valObj(1,null);
exception when VALUE_ERROR then return valObj(0,'Invalid number. Valid formats are: 12345, 12345.67, -12345, etc...');
end;
end if;
end;
/
May also want to consider utilizing pls_integer over number. Don't know if it will buy you much, but documents suggest some gain will be had.
http://docs.oracle.com/cd/B10500_01/appdev.920/a96624/03_types.htm states,
"You use the PLS_INTEGER datatype to store signed integers. Its magnitude range is -2*31 .. 2*31. PLS_INTEGER values require less storage than NUMBER values. Also, PLS_INTEGER operations use machine arithmetic, so they are faster than NUMBER and BINARY_INTEGER operations, which use library arithmetic. For efficiency, use PLS_INTEGER for all calculations that fall within its magnitude range."