one of my assignment question is:
The method AvgReviewScore() returns the average review score for an album excluding scores from anonymous reviewers, i.e. reviews with null REVIEWER_NAME.
So firstly i wrote SQL:
(This is oriented database assignment)
select deref(b.album).title
,round(avg(b2.reviewscore),2)
from album_artist_table b,table(deref(b.album).review) b2
where deref(b.artist).aname like '%Joe%'
and b2.reviewername is not null
group by deref(b.album).title;
Can i ask how to translate this sql to member function?
I try to create a type called:AvgReviewScore_type
and then i create type body member function as following:
create or replace TYPE BODY ALBUM_TYPE AS
member function AvgReviewScore return AvgReviewScore_type AS
AVGtable AvgReviewScore_type := AvgReviewScore_type(null,null);
BEGIN
select deref(b.album).title,round(avg(b2.reviewscore),2)
into AVGtable
from album_artist_table b,table(deref(b.album).review) b2
where b2.reviewername is not null
group by deref(b.album).title;
return AVGtable;
END AvgReviewScore;
END;
But it doesn't work, is there anybody know about the reason?
I guess your question has incomplete information and depending on whatever given see the below example. I hope it would help you.
Create type exmpl_type as object (
num number,
member function func(p in number) return number
);
/
create type body exmpl_type as
member function func(p in number)
return number is
begin
return num/p;
end func;
end;
/
Related
to test the code:
https://dbfiddle.uk/?rdbms=oracle_21&fiddle=b02671a25f9d7949e0b55ca59084ecd1
If I define a record as an object, I can call it in a sql statement this way objectname(field1, field2)
But If I define a record inside package as a record. I can't do that anymore.
create TYPE arguments_r IS object
(
q integer,
b INTEGER
);
CREATE FUNCTION f (p IN arguments_r) RETURN INTEGER
IS
BEGIN
RETURN 1;
END;
/
select arguments_r(1,1) from dual -- not printed but exists nevertheless. THe following statement prove it.
select f(arguments_r(1,1)) from dual --print the expected result
CREATE PACKAGE pck
as
TYPE arguments_r IS record
(
q integer,
b INTEGER
);
FUNCTION f (p IN pck.arguments_r) RETURN INTEGER;
end;
CREATE PACKAGE body pck
as
FUNCTION f (p IN pck.arguments_r) RETURN INTEGER
is
begin
return 1;
end;
END;
select pck.arguments_r(1,1) from dual -- ORA-06553: PLS-306: wrong number or types of arguments in call to 'ARGUMENTS_R'
[TL;DR] In general, you cannot.
A record is a PL/SQL ONLY data type and it CANNOT be used in SQL. If you want to use the data in SQL then you will need to put it into an SQL data type such as an Object.
There are ways of achieving what you want via PIPELINED functions (which implicitly convert a RECORD to an OBJECT so, technically you never use a PL/SQL data type in the SQL scope but it certainly looks like you do) but the implementation is convoluted and if I ever did a code review on code that tried to use records the way I show below then I would fail it in the review and tell the author to just use an SQL OBJECT.
Given the setup:
CREATE PACKAGE pck as
TYPE args_r IS record (
q integer,
b INTEGER
);
TYPE args_t IS TABLE OF args_r;
FUNCTION f RETURN args_t PIPELINED;
FUNCTION f2 RETURN args_t;
FUNCTION create_arg(q INTEGER, b INTEGER) RETURN args_t PIPELINED;
FUNCTION create_arg2(q INTEGER, b INTEGER) RETURN args_t;
FUNCTION g(args IN args_r) RETURN INTEGER;
END;
/
CREATE PACKAGE BODY pck as
FUNCTION f RETURN args_t PIPELINED
IS
BEGIN
PIPE ROW (args_r(1,1));
PIPE ROW (args_r(2,2));
END;
FUNCTION f2 RETURN args_t
IS
BEGIN
RETURN args_t(args_r(1,1), args_r(2,2));
END;
FUNCTION create_arg(q INTEGER, b INTEGER) RETURN args_t PIPELINED
IS
BEGIN
PIPE ROW (args_r(q, b));
END;
FUNCTION create_arg2(q INTEGER, b INTEGER) RETURN args_t
IS
BEGIN
RETURN args_t(args_r(q, b));
END;
FUNCTION g(args IN args_r) RETURN INTEGER
IS
BEGIN
RETURN args.q;
END;
END;
/
From PL/SQL to SQL via a PIPELINED function:
If you want to pass PL/SQL records from a PL/SQL function into an SQL query then it will "work" (see the comment below for clarification of why) if you use a PIPELINED function then:
SELECT *
FROM TABLE(pck.f());
Outputs:
Q
B
1
1
2
2
BUT that is not because you can use PL/SQL records in SQL it is because a PIPELINED function is intended to be used in the SQL scope so Oracle has implicitly created a duplicate OBJECT data type (and TABLE OF ... data type) and mapped the PL/SQL record to the SQL object and despite what the signature of the function says, it is not returning records and returning Objects instead.
From PL/SQL to SQL via a function returning a collection:
If you try to do exactly the same thing with a non-PIPELINED function:
SELECT *
FROM TABLE(pck.f2());
Then you get the error:
ORA-00902: invalid datatype
Because no such implicit conversion has been applied and you CANNOT use PL/SQL record types in SQL.
From SQL to PL/SQL:
Going the other way, as per the question, and trying to create records in the SQL scope and pass them to a PL/SQL function then it CANNOT work because it is impossible to create PL/SQL records in the SQL scope.
The query:
SELECT pck.g(pck.args_r(1,1))
FROM DUAL;
Fails with the error:
ORA-06553: PLS-306: wrong number or types of arguments in call to 'ARGS_R'
From PL/SQL to SQL back to PL/SQL via a PIPELINED function:
If you create the record in PL/SQL and return it via a PIPELINED function then it "works":
SELECT pck.g((SELECT VALUE(t) FROM TABLE(pck.create_arg(1,1)) t))
FROM DUAL;
and outputs:
PCK.G((SELECTVALUE(T)FROMTABLE(PCK.CREATE_ARG(1,1))T))
1
From PL/SQL to SQL back to PL/SQL via a function returning a collection:
However, if you use a non-PIPELINED function:
SELECT pck.g((SELECT VALUE(t) FROM TABLE(pck.create_arg2(1,1)) t))
FROM DUAL;
Then it raises the exception:
ORA-00902: invalid datatype
db<>fiddle here
Conclusion:
Just define an OBJECT data type; do not try to use convoluted methods to ram PL/SQL only data types into an SQL scope where they are not meant to be used.
I have this code :
CREATE OR REPLACE TYPE t_abonnement_type AS OBJECT
(
ref_abonnement_type NUMBER,
type_abonne VARCHAR(50),
MEMBER PROCEDURE DISPLAY
);
CREATE OR REPLACE TYPE t_abonnement AS OBJECT
(
ref_abonnement NUMBER,
date_debut DATE,
type_abonnement REF t_abonnement_type,
MEMBER PROCEDURE DISPLAY
);
What i want to do is just create the members procedures DISPLAY declared.
So i did it this way :
CREATE OR REPLACE TYPE BODY t_abonnement AS
MEMBER PROCEDURE DISPLAY IS
BEGIN
/* SOME CODE */
type_abonnement.display;
END;
END;
And i get this error
PLS-00536: Navigation through REF variables is not supported in PL/SQL.
So how can i deal with REF in PL/SQL statements ?
Thanks
As mentioned by #APC, it's not possible to directly use member function of a Object to another using REF since Oracle supports them in SQL but not in PL/SQL.
However if I look at your requirement from a different angle, I could see you are trying to simply make use of Procedure used in an Object to another Object. Means if I forget the referencing part and create a scenario which could satisfy the referencing concept then it should "OK".
This is possible. Yes..!!! This can be done. The only thing I did here is to make the two Objects as parent-child to achieve referencing. See demo below:
--Parent Object
CREATE OR REPLACE TYPE t_abonnement_type AS OBJECT
(
ref_abonnement_type NUMBER,
type_abonne VARCHAR(50),
MEMBER FUNCTION DISPLAY return varchar2
) NOT FINAL;
-- Member Funtion Body
CREATE OR REPLACE TYPE BODY t_abonnement_type
AS
MEMBER FUNCTION DISPLAY
return varchar2
IS
BEGIN
return ('Hi');
END DISPLAY;
END;
Testing my Parent Object:
SQL> SELECT t_abonnement_type(1,'a').display() col from DUAL;
COL
---
Hi
Child Object to achieve referencing concept
CREATE OR REPLACE TYPE t_abonnement
under t_abonnement_type
(
ref_abonnement NUMBER,
date_debut DATE,
MEMBER function V_DISPLAY return varchar2
);
--Member function
CREATE OR REPLACE TYPE BODY t_abonnement
AS
MEMBER Function V_DISPLAY return varchar2
IS
var varchar2(10);
BEGIN
var:= t_abonnement_type(1,'A').display(); --Calling Parent Member function here
return('Called from Child Object -->'||var);
END;
END;
Testing my Child Object to check if the parent Member function is referenced or not:
SQL> SELECT T_ABONNEMENT(1,'A',2,TO_DATE('30-JUN-2018','DD-MON-YYYY')).V_DISPLAY() COL FROM DUAL;
Col
---
Called from Child Object -->Hi
Note I haven't used overloading done in your code as I feel overloading makes the stuff harder to understand as an individual as well as for compiler.
I'm trying to create object data types in Oracle.
I have an object type called type_transaction, I use this type of body
EXTENDS functions.
The compiler crashes the following error:
"identifier 'EXTENDS' must be declared.
I do not know what I'm doing wrong
This is type :
CREATE OR REPLACE TYPE type_transakcja AS OBJECT
(
id_transakcji NUMBER(10),
data_transakcji DATE,
pracownika_id REF type_pracownik,
klient_id REF type_klienta,
produkt_id REF type_produkt,
zamowienia k_zamowienie,
MAP MEMBER FUNCTION odwzoruj RETURN NUMBER,
MEMBER FUNCTION wartosc_zamowienia RETURN NUMBER,
MEMBER FUNCTION transakcja(produkty REF type_produkt, ile NUMBER)
RETURN NUMBER,
MEMBER PROCEDURE anuluj,
MEMBER PROCEDURE zatwierdz
);
CREATE TYPE k_zamowienie AS TABLE OF type_sprzedaz;
and this is body of type
CREATE OR REPLACE TYPE BODY type_transakcja IS
MEMBER FUNCTION transakcja(produkty REF type_produkt, ile NUMBER)
RETURN NUMBER IS
i INTEGER;
t k_zamowienie;
p type_produkt;
BEGIN
SELECT DEREF(produkty) INTO p FROM DUAL;
IF p.ilosc_magazynowa - ile >=0 THEN
p.ilosc_magazynowa:=p.ilosc_magazynowa-ile;
t:=SELF.zamowienia;
t:=extend(1);
i:= SELF.zamowienia.last();
t(i).ilosc:=ile;
t(i).cena:=SELF.wartosc_zamowienia();
t(i).produkt_id:=produkty;
RETURN 1;
ELSE
RETURN 0;
END IF;
END;
END;
It's hard to be sure from the limited information you've provided, and what seems to be not quite the actual error you get; but it looks like you're just extending your collection incorrectly.
Assuming k_zamowienie is a table type, you're doing:
t:=extend(1);
instead of:
t.extend(1);
And you don't really need i, and if you do have it your index is going to be one off (as it's based on the size of the original table, not the cloned-and-extended on); you can do:
t(t.last).ilosc:=ile;
t(t.last).cena:=SELF.wartosc_zamowienia();
t(t.last).produkt_id:=produkty;
But even then you aren't doing anything with t, so this seems a bit pointless. Maybe you meant this?
zamowienia.extend(1);
zamowienia(zamowienia.last()).ilosc:=ile;
zamowienia(zamowienia.last()).cena:=SELF.wartosc_zamowienia();
zamowienia(zamowienia.last()).produkt_id:=produkty;
I have the following scenario:
I need to feed into either a Procedure or Function the Following Parameters:
Link_1_ID, Link_2_ID, Address1, Address2, City, State, Zip, Address_Type
These will be used to query a table (we'll call the Table ADDRESS_INFO) surrounding some logic and then hardcode the variable LOC_CDE with '0001'.
I then need this all to return in the form of a User Defined Table type (though it should only pull one record at a time).The table would output the following:
Link_1_ID, Link_2_ID, Address1, Address2, City, State, Zip, LOC_CDE (no Address_Type)
I am VERY new to PL/SQL and have had little luck in returning anything. I would also prefer to keep ALL code in one defined PACKAGE (which is also causing trouble).
Any advice or help would be appreciated.
You want your own UDT, saying that your function will only return one value; so I believe you may first need to define your type, for example:
create or replace type yourType as object (fieldA number, fieldB varchar2(20), fieldC varchar2(20));
Then you can define your function with all the parameters you need, returning a single instance of your UDT:
create or replace function yourFunction (parA number, parB varchar2) return yourType is
returnVal yourType;
begin
select yourType(parA, parB || ' something', 'FIXED_VALUE')
into returnVal
from dual;
return returnVal;
end;
This is only one way to handle your UDT variables; for example you could fill the fields of you UDT explicitly:
create or replace function yourFunction (parA number, parB varchar2) return yourType is
returnVal yourType;
begin
returnVal := new yourType(null, null, null); /* you need an initialization here */
select parA, parB || ' something', 'FIXED_VALUE'
into returnVal.fieldA, returnVal.fieldB, returnVal.fieldC
from dual;
return returnVal;
end;
You could use some other variables to fetch into and then use these variables to fill your return value, or define a constructor to handle the creation of your UDT instances with some custom logic, and so on... this is just a very basic example of simple ways to handle a UDT and use it as a return value of a function.
I coded for the function, and calling procedure below. Both of coding is possible to execute, but when I exec ProdDetails(10010), it show error. Can anybody know what are problems?
create or replace function ProdCheck(
f_product_id in number)
return varchar IS
f_product_name varchar(30);
f_product_price number;
begin
select product_name, price into f_product_name, f_product_price
from product
where product_id = f_product_id;
return f_product_name;
end;
create or replace procedure ProdDetails(
sp_product_id in number
)IS
f_product_id number;
sp_name varchar(30);
sp_price number;
begin
f_product_id := ProdCheck(sp_product_id);
if f_product_id > 0 then
dbms_output.put_line('Product Name : '||sp_name);
dbms_output.put_line('Product Price : '||sp_price);
else
dbms_output.put_line('Product not in the database!');
end if;
end;
Your function prodcheck takes a product_id and returns the product_name. Then in the procedure you call the function, you feed the function a product_id (everything fine so far), but then assign the return value from the function, which is the product name, to the variable f_product_id, which you declared as number. Obviously that won't work. And, indeed, the function and the procedure are both syntactically correct; only when you put them together will this fail, and only at runtime since Oracle doesn't strictly enforce data types (if the product name was '1000' instead, perhaps the function would execute OK - and produce garbage results since it would interpret this product name as the product id instead).
You query your table in the function to check if the product id is valid, and you return the name. In the procedure, you could assign the return value from the function to sp_name. The function does not return the price though (it can't - a function cannot return more than one value), so you can't display the price in the procedure. You could query the table again in the procedure, but that seems pretty senseless; it would be better to combine everything into a single procedure. (You probably don't need the check at all - if the product id doesn't exist in the table, you will get a "no rows" exception and you can use that instead of prodcheck.)