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

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).

Related

In PostgreSQL what does the CREATE AGGREGATE option SORTOP do?

From the Postgres documentation (https://www.postgresql.org/docs/9.6/sql-createaggregate.html) I find it hard to deduce what the parameter SORTOP does.
Is this option only applicable to an ordered-set aggregate?
Concretely I'm trying to create an aggregate function that finds the most frequent number in a column of numbers. I thought specifying the SORTOP option would sort the data before executing my self defined aggregate function, but this doesn't seem to be the case.
Here is my current implementation that only works when the input data is sorted.
It loops over the rows and keeps track of the largest sequence of previous numbers (largfreq variables in state) and the amount of repetitions seen so far of the number that it's currently on (currfreq variables in state).
CREATE TYPE largfreq_state AS (
largfreq_val INT,
largfreq INT,
currfreq_val INT,
currfreq INT
);
CREATE FUNCTION slargfreq(state largfreq_state, x INT) RETURNS largfreq_state AS $$
BEGIN
if state.currfreq_val <> x then
if state.currfreq >= state.largfreq then
state.largfreq = state.currfreq;
state.largfreq_val = state.currfreq_val;
end if;
state.currfreq = 1;
state.currfreq_val = x;
else
state.currfreq = state.currfreq + 1;
end if;
return state;
END;
$$ language plpgsql;
CREATE FUNCTION flargfreq(state largfreq_state) RETURNS INT AS $$
BEGIN
if state.currfreq >= state.largfreq then
return state.currfreq_val;
else
return state.largfreq_val;
end if;
END;
$$ language plpgsql;
CREATE AGGREGATE largfreq(INT) (
SFUNC = slargfreq,
STYPE = largfreq_state,
FINALFUNC = flargfreq,
INITCOND = '(0, 0, 0, 0)',
SORTOP = <
);
This is well explained in the documentation:
Aggregates that behave like MIN or MAX can sometimes be optimized by looking into an index instead of scanning every input row. If this aggregate can be so optimized, indicate it by specifying a sort operator. The basic requirement is that the aggregate must yield the first element in the sort ordering induced by the operator; in other words:
SELECT agg(col) FROM tab;
must be equivalent to:
SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
So you need that for aggregates that can be calculated using an index scan.

Returning multiple values using function causing multiple query runs

We have kiosks for customers to check their purchase volume for two different categories of items. They will input their mobile number, which will send an OTP to their mobile numbers and they will input it back to authenticate, the system has to check the data and display for them. As a developer, the kiosk supplier has provided us with a limited functionality development kit by which we can execute select statement on the database and display the returned values on the kiosk.
I have created an object type as follows:
CREATE OR REPLACE TYPE rebate_values
AS
OBJECT (ASales_total number,
ACurrent_Rebate_Percent number,
ANeeded_Sales number,
ANext_Rebate_Percent number,
BSales_total number,
BCurrent_Rebate_Percent number,
BNeeded_Sales number,
BNext_Rebate_Percent number);
A function to which I will pass customers' mobile to get their sales and rebate information:
CREATE OR REPLACE FUNCTION AA_rebate_function (P_phone IN NUMBER)
RETURN rebate_values
IS
A_P_Sales_total NUMBER;
A_P_Current_Rebate_Percent NUMBER;
A_P_Needed_Sales NUMBER;
A_P_Next_Rebate_Percent NUMBER;
B_P_Sales_total NUMBER;
B_P_Current_Rebate_Percent NUMBER;
B_P_Needed_Sales NUMBER;
B_P_Next_Rebate_Percent NUMBER;
P_CODE VARCHAR (10);
BEGIN
SELECT CC_CODE
INTO P_CODE
FROM CUSTOMERS
WHERE C_MOBILE = P_phone;
FOR OUTDATA
IN (
--My Query to retrieve the data
Select ................
)
LOOP
IF OUTDATA.CLASS = 'X'
THEN
A_P_Sales_total := OUTDATA.SALES_TOTAL;
A_P_Current_Rebate_Percent := OUTDATA.CURRENT_REBATE_PERCENT;
A_P_Needed_Sales := OUTDATA.NEEDED_SALES_FOR_HIGHER_REBATE;
A_P_Next_Rebate_Percent := OUTDATA.NEXT_HIGHER_REBATE_PERCENT;
END IF;
IF OUTDATA.CLASS = 'Y'
THEN
B_P_Sales_total := OUTDATA.SALES_TOTAL;
B_P_Current_Rebate_Percent := OUTDATA.CURRENT_REBATE_PERCENT;
B_P_Needed_Sales := OUTDATA.NEEDED_SALES_FOR_HIGHER_REBATE;
B_P_Next_Rebate_Percent := OUTDATA.NEXT_HIGHER_REBATE_PERCENT;
END IF;
END LOOP;
RETURN rebate_values (A_P_Sales_total,
A_P_Current_Rebate_Percent,
A_P_Needed_Sales,
A_P_Next_Rebate_Percent,
B_P_Sales_total,
B_P_Current_Rebate_Percent,
B_P_Needed_Sales,
B_P_Next_Rebate_Percent);
END;
/
The query takes 27 seconds to retrieve the values for each customer. Each customer will have 2 rows, so that's why I have used LOOP to collect the values.
When I execute the function:
SELECT AA_rebate_function (XXXXXXXXXX) FROM DUAL;
I get data as follows in a single column within 27 seconds:
(XXXX, X, XXXX, X, XXXX, X, XXXX, X)
But when I execute the function to get the values in different columns, it takes 27 x 8 seconds = 216 seconds, i.e., approximately 3.6 minutes which is a big issue as the customer cannot wait for 3.6 minutes on the kiosk to view the data.
SELECT x.c.ASales_total,
x.c.ACurrent_Rebate_Percent,
x.c.ANeeded_Sales,
x.c.ANext_Rebate_Percent,
x.c.BSales_total,
x.c.BCurrent_Rebate_Percent,
x.c.BNeeded_Sales,
x.c.BNext_Rebate_Percent
FROM (SELECT AA_rebate_function (XXXXXXXXXX) c FROM DUAL) x;
I have tried using stored procedure with OUT values but it doesn't fit in my environment as I cannot program to execute stored procedures from the kiosk development toolkit because it only supports select statements, checked with the supplier and they don't have any plan to add that support in near future.
I tried converting the single field into multiple columns using REGEXP_SUBSTR but I get a type conversion error as it is an array.
The query is very complex and has to calculate data for the last 10 years and has millions of rows, 27 seconds is actually the optimum time to get the desired results.
Interesting! I didn't realize that when you query a function that returns an object, it runs the function once for each column you reference the object in. That's awkward.
The easiest solution I could find for this is to switch your function to be PIPELINED. You'll need to create a nested table type to do this.
create type rebate_values_t is table of rebate_values;
/
CREATE OR REPLACE FUNCTION AA_rebate_function (P_phone IN NUMBER)
RETURN rebate_values_t PIPELINED
IS
... your code here ...
PIPE ROW (rebate_values (A_P_Sales_total,
A_P_Current_Rebate_Percent,
A_P_Needed_Sales,
A_P_Next_Rebate_Percent,
B_P_Sales_total,
B_P_Current_Rebate_Percent,
B_P_Needed_Sales,
B_P_Next_Rebate_Percent));
RETURN;
END;
/
SELECT x.ASales_total,
x.ACurrent_Rebate_Percent,
x.ANeeded_Sales,
x.ANext_Rebate_Percent,
x.BSales_total,
x.BCurrent_Rebate_Percent,
x.BNeeded_Sales,
x.BNext_Rebate_Percent
FROM TABLE(AA_rebate_function (XXXXXXXXXX)) x;
For some reason, this should only execute the function once, and take 27 seconds.

ORACLE SQL Method Produces Warning

I'm having trouble getting the following member method to compile (count_single_buses). Would appreciate any advice on what might be wrong syntactically with my code.
CREATE OR REPLACE TYPE BodyModel2_Type AS OBJECT(
ModelID INTEGER,
ModelName VARCHAR2(45),
FloorType VARCHAR2(45),
Manufacturer VARCHAR2(45),
Length NUMBER(8,2),
Width NUMBER(8,2),
NoOfAxles INTEGER,
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN INTEGER);
/
CREATE OR REPLACE TYPE BODY BodyModel2_Type AS
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN INTEGER IS
N INTEGER;
BEGIN
N := (SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S
WHERE S.BODYMODELREF = ModelID);
RETURN N;
END count_single_buses;
END;
--EDIT--
Thanks to #Ravi, I managed to solve the issue my correcting my SQL syntax and setting the resultset to a NUMBER, instead of INTEGER.
CREATE OR REPLACE TYPE BODY BodyModel_Type AS
MEMBER FUNCTION count_single_buses(thisModelID INTEGER) RETURN NUMBER IS
NUM NUMBER;
BEGIN
SELECT COUNT(S.BODYMODELREF) INTO NUM FROM SINGLEDECKBUS_TABLE S WHERE S.BODYMODELREF.MODELID = thisModelID;
RETURN NUM;
END count_single_buses;
END;
/
Still not sure why #Ravi's exact code still produced the warning, and thought that resultset when returning a count value could go into an integer. At any rate, the code works now. Thanks all.
Your BodyModel2_Type Type definition looks okay. However, the body definition is syntactically incorrect.
You cannot define a SQL statement directly to a variable, thus making this statement wrong.
N := (SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S
WHERE S.BODYMODELREF = ModelID);
You will have to use Select... into statement in order to assign the result set of your SQL query into a variable. So, the right syntax should look like this
SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S INTO N
WHERE S.BODYMODELREF = ModelID
AFAIK you don't have END the Type followed by the Type name like this END count_single_buses. It'll produce an error. So, overall your Type body specification should look like this:
CREATE OR REPLACE TYPE BODY BodyModel2_Type AS
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN NUMBER IS
N NUMBER;
BEGIN
SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S INTO N
WHERE S.BODYMODELREF = ModelID;
RETURN (N);
END;
END;
/
I'm writing this off without any live environment available right now so please let me know if you come across any error in the above code.
Cheers.

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."

Custom aggregate function inside a package

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.