Here is my object def:
CREATE OR REPLACE TYPE FALCON.contacts AS OBJECT (phone VARCHAR2(50)
,phoneusage VARCHAR2(25)
,phonetype VARCHAR2(25)
,email VARCHAR2(150)
,phoneext VARCHAR2(25)
,anytext VARCHAR2(250))
Here is the table def:
CREATE OR REPLACE TYPE FALCON.contacttbl AS TABLE OF contacts
Here is my pipelined function
FUNCTION get_pcontacts(p_conttbl IN xmltypedefs_spec.conttbl)
RETURN falcon.contacttbl
PIPELINED
IS
l_contact falcon.contacts;
BEGIN
FOR n IN 1 .. p_conttbl.count
LOOP
PIPE ROW(**falcon.contacts**(p_conttbl(n).phone, p_conttbl(n).phoneusage, p_conttbl(n).phonetype, p_conttbl(n).email, p_conttbl(n).phoneext, p_conttbl(n).anytext));
END LOOP;
RETURN;
END get_pcontacts;
I am getting the error when I call the table function here:
FUNCTION get_pidxml(p_pidrec xmltypedefs_spec.pidtyp)
RETURN CLOB
IS
l_tmprec CLOB;
l_pxml xmltype;
l_bxml xmltype;
l_pcontacts xmltypedefs_spec.conttbl := p_pidrec.personalcont;
l_bcontacts xmltypedefs_spec.conttbl := p_pidrec.businesscont;
BEGIN
-- l_pxml := get_contacts(p_pidrec, 'p');
-- l_bxml := get_contacts(p_pidrec, 'b');
SELECT xmlelement("pid"
,xmlforest(p_pidrec.setid AS "setID"
,p_pidrec.patidexternal AS "patientIDExternal"
,p_pidrec.patientid AS "patientID"
,p_pidrec.patintasgnauth AS "patientIDInterAssignAuthority"
,p_pidrec.patinttypecd AS "patientIDInternalIDTypeCode"
,p_pidrec.patidalternate1 AS "patientIDAlernate1"
,p_pidrec.patlastname AS "patientLastName"
,p_pidrec.patfirstname AS "patientFirstName"
,p_pidrec.patmiddleinit AS "patientMiddleInitial"
,p_pidrec.patsuffix AS "patientSuffix"
,p_pidrec.patprefix AS "patientPrefix"
,p_pidrec.degree AS "degree"
,p_pidrec.familyname AS "familyName"
,p_pidrec.givenname AS "givenName"
,p_pidrec.mothermaidname AS "mothersMaidenName"
,p_pidrec.dob AS "dateOfBirth"
,p_pidrec.adminsex AS "administrativeSex"
,p_pidrec.patientalias AS "patientAlias"
,p_pidrec.race AS "race"
,p_pidrec.racetext AS "raceText"
,p_pidrec.pataddr1 AS "patientAddress1"
,p_pidrec.pataddr2 AS "patientAddress2"
,p_pidrec.patcity AS "patientCity"
,p_pidrec.patstate AS "patientState"
,p_pidrec.patzip AS "patientZip"
,p_pidrec.countrycode AS "countryCode"
,p_pidrec.addresstype AS "addressType"
,p_pidrec.othgeodesig AS "otherGeographicDesignation"
,p_pidrec.county AS "county"
,(SELECT xmlagg(xmlelement("contactInfo",
xmlforest(phone AS "phoneNumber",
phoneusage AS "telecomUseCode",
phonetype AS "telecomequiptype",
email AS "email",
phoneext AS "phonenumberextension",
anytext AS "anytext")))
FROM TABLE(**get_pcontacts(l_pcontacts**))) AS "personalContact"
http://pls-00382.ora-code.com/
PLS-00382: expression is of wrong type
Since I don't know how xmltypedefs_spec.conttbl is defined, I removed the input parameter from the pipelined function and just had it generate fake data on the fly:
CREATE OR REPLACE FUNCTION get_contacts
RETURN contacttbl PIPELINED
IS
-- converts some structure to pipe of contacts
BEGIN
FOR n IN 1 .. 5 LOOP
PIPE ROW(
contact(
'877-867-5309',
'Work',
'Cell',
'jenny#gmail.com',
n,
'WTF?'
)
);
END LOOP;
RETURN;
END get_contacts;
The subquery now executes without error:
SELECT
xmlagg(
xmlelement("contactInfo",
xmlforest(
phone AS "phoneNumber",
phoneusage AS "telecomUseCode",
phonetype AS "telecomequiptype",
email AS "email",
phoneext AS "phonenumberextension",
anytext AS "anytext"
)
)
)
FROM
TABLE( get_contacts( ) )
This tells me there is probably something wrong with xmltypedefs_spec.conttbl, perhaps in using a collection type within an SQL statement? Not sure. What if you changed xmltypedefs_spec.pidtyp to use the falcon.contacttbl instead of xmltypedefs_spec.conttbl. Seems like you've got one package type and one object type that are doing the same thing?
xmltypedefs_spec defines record types that correspond to XML elements. These record types are used to shred and build XML. Originally, the XML did not use repeating elements, but now must. I am attempting to take a table of xmltypedefs_spec.pidtyp and use the pipelined function to return 'rows' of data from an associative table. It is in this fashion that I want to send rows of array records to build xml.
Related
I'm working on a trigger.
declare
v_time number(11, 0);
begin
for i in 0..1
loop
select JSON_VALUE(body, '$.sections[0].capsules[0].timer.seconds') into v_time from bodycontent where contentid=1081438;
dbms_output.put_line(v_time);
end loop;
end;
However, index references do not become dynamic.
like JSON_VALUE(body, '$.sections[i].capsules[i].timer.seconds')
Is there any way I can do this?
You can use JSON_TABLE:
declare
v_time number(11, 0);
begin
for i in 0..1 loop
SELECT time
INTO v_time
FROM bodycontent b
CROSS APPLY
JSON_TABLE(
b.body,
'$.sections[*]'
COLUMNS (
section_index FOR ORDINALITY,
NESTED PATH '$.capsules[*]'
COLUMNS (
capsule_index FOR ORDINALITY,
time NUMBER(11,0) PATH '$.timer.seconds'
)
)
) j
WHERE j.section_index = i + 1
AND j.capsule_index = i + 1
AND b.contentid=1081438;
dbms_output.put_line(v_time);
end loop;
end;
/
Which, for the test data:
CREATE TABLE bodycontent ( body CLOB CHECK ( body IS JSON ), contentid NUMBER );
INSERT INTO bodycontent ( body, contentid ) VALUES (
'{"sections":[
{"capsules":[{"timer":{"seconds":0}},{"timer":{"seconds":1}},{"timer":{"seconds":2}}]},
{"capsules":[{"timer":{"seconds":3}},{"timer":{"seconds":4}},{"timer":{"seconds":5}}]},
{"capsules":[{"timer":{"seconds":6}},{"timer":{"seconds":7}},{"timer":{"seconds":8}}]}]}',
1081438
);
Outputs:
0
4
Or, you can just use a query:
SELECT section_index, capsule_index, time
FROM bodycontent b
CROSS APPLY
JSON_TABLE(
b.body,
'$.sections[*]'
COLUMNS (
section_index FOR ORDINALITY,
NESTED PATH '$.capsules[*]'
COLUMNS (
capsule_index FOR ORDINALITY,
time NUMBER(11,0) PATH '$.timer.seconds'
)
)
) j
WHERE ( j.section_index, j.capsule_index) IN ( (1,1), (2,2) )
AND b.contentid=1081438;
Which outputs:
SECTION_INDEX | CAPSULE_INDEX | TIME
------------: | ------------: | ---:
1 | 1 | 0
2 | 2 | 4
(Note: the indexes from FOR ORDINALITY are 1 higher than the array indexes in the JSON path.)
db<>fiddle here
You would need to concatenate the variable in the json path:
JSON_VALUE(body, '$.sections[' || to_char(i) || '].capsules[0].timer.seconds')
I don't really see how your question relates to a trigger.
So, the problem is that you want to loop over an index (to go diagonally through the array of arrays, picking up just two elements) - but the JSON_* functions don't work with variables as array indices - they require hard-coded indexes.
PL/SQL has an answer for that - native dynamic SQL, as demonstrated below.
However, note that this approach makes repeated calls to JSON_VALUE() over the same document. Depending on the actual requirement (I assume the one in your question is just for illustration) and the size of the document, it may be more efficient to make a single call to JSON_TABLE(), as illustrated in MT0's answer. If in fact you are indeed only extracting two scalar values from a very large document, two calls to JSON_VALUE() may be justified, especially if the document is much larger than shown here; but if you are extracting many scalar values from a document that is not too complicated, then a single call to JSON_TABLE() may be better, even if in the end you don't use all the data it produces.
Anyway - as an illustration of native dynamic SQL, here's the alternative solution (using MT0's table):
declare
v_time number(11, 0);
begin
for i in 0..1 loop
execute immediate q'#
select json_value(body, '$.sections[#' || i ||
q'#].capsules[#' || i || q'#].timer.seconds')
from bodycontent
where contentid = 1081438#'
into v_time;
dbms_output.put_line(v_time);
end loop;
end;
/
0
4
PL/SQL procedure successfully completed.
You can see the example below:
Just hit this query in your console and try to understand the implementation.
SELECT JSON_VALUE('{
"increment_id": "2500000043",
"item_id": "845768",
"options": [
{
"firstname": "Kevin"
},
{
"firstname": "Okay"
},
{
"lastname": "Test"
}
]
}', '$.options[0].firstname') AS value
FROM DUAL;
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).
so I'm really just trying to output the lines to the 12 days of Christmas, just to get a handle on loops in PL/SQL, and I can't even seem to get a simple array declared correctly. Here is the code:
CREATE OR REPLACE
TYPE all_gifts is object (first_words varchar2(20), second_words varchar2(50));
/
DECLARE
type gifts is table of all_gifts;
type daysarray IS VARRAY(12) OF VARCHAR2(20);
lv_gifts GIFTS := gifts( all_gifts('and a','Partridge in a pear tree')
, all_gifts('Two','Turtle Doves')
, all_gifts('Three','French Hens')
, all_gifts('Four','Calling Birds')
, all_gifts('Five','Golden Rings')
, all_gifts('Six','Geese a laying')
, all_gifts('Seven','Swans a Swimming')
, all_gifts('Eight','Maids a milking')
, all_gifts('Nine','Ladies Dancing')
, all_gifts('Ten','Lords a leaping')
, all_gifts('Eleven','Pipers piping')
, all_gifts('Twelve','Drummers drumming')
);
second_array_elem varchar2(50);
lv_counter NUMBER := 1;
lv_days daysarray;
lv_first_word VARCHAR2(50);
BEGIN
lv_days := daysarray('First','Second','Third','Fourth','Fifth','Sixth','Seventh','Eigth','Ninth','Tenth','Eleventh','Twelefth');
FOR day in 1 .. lv_days.count loop
IF day != 'first' THEN
lv_first_word := second_array_elem;
ELSE
lv_first_word := 'A';
second_array_elem := lv_gifts.first;
END IF;
dbms_output.put_line('On the ['||lv_days(day)||'] day of Christmas');
second_array_elem := lv_gifts.next(second_array_elem);
END LOOP;
END;
/
And the console is throwing an error on the line right after my BEGIN statement (where I'm trying to declare the lv_days array) saying:
numeric or value error: character to number conversion error
But above, in my declare statement, I have it as a varying array of 12 indexes with the type of varchar2(20).
What simple step did I miss here?
Issue is with the below line of code..
IF day != 'first' THEN
As per the code, system is trying to compare the loop variable with string 'first'
You may use the below code
IF lv_days(day) != 'first' THEN
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.
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."