Here is my situation: type A wants to have a method that returns a type that is a table of type A entries. Can I do this?
Here is a pair of SQL Type declarations:
SQL> create or replace type a as object
2 ( attr1 number
3 , attr2 date )
4 /
Type created.
SQL> create or replace type a_nt as table of a
2 /
Type created.
SQL>
Now what we want is a method which returns A_NT(). Hmm, let's see:
SQL> alter type a
2 add member function getme (p1 number) return a_nt
3 /
alter type a
*
ERROR at line 1:
ORA-04055: Aborted: "A" formed a non-REF mutually-dependent cycle with "A_NT".
SQL>
Uh-oh. We need another approach. This is the sort of occasion when we should be using inheritance.
SQL> create or replace type abstract_a as object
2 ( attr1 number
3 , attr2 date )
4 not final not instantiable
5 /
Type created.
SQL> create or replace type a_nt as table of abstract_a
2 /
Type created.
SQL> create or replace type a under abstract_a
2 ( member function getme (p1 number) return a_nt )
3 instantiable
4 /
Type created.
SQL>
Looks good. So, we'll add an implementation and then try it out:
SQL> create or replace type body a as
2 member function getme (p1 number) return a_nt
3 is
4 l_nt a_nt;
5 begin
6 select a(empno, hiredate)
7 bulk collect into l_nt
8 from emp
9 where deptno = p1;
10 return l_nt;
11 end;
12 end;
13 /
Type body created.
SQL>
Let's roll!
SQL> set serveroutput on
SQL> declare
2 v a := a(null, null);
3 n a_nt;
4 begin
5 n := v.getme(50);
6
7 for i in n.first()..n.last() loop
8 dbms_output.put_line(n(i).attr1 ||'::'||to_char(n(i).attr2, 'DD-MON-YYYY'));
9 end loop;
10 end;
11 /
8085::08-APR-2010
8060::08-APR-2008
8061::27-FEB-2010
8100::
PL/SQL procedure successfully completed.
SQL>
The bootstrapping is cumbersome but it works. This may be yet another area where Oracle's OOP implementation is incomplete. But perhaps it's just a reflection of the fact that is not a good idea. It took me a while to figure it out, because the underlying model doesn't really make sense. Although maybe with Real World names it would have become obvious :)
Related
I need to convert a Pl/SQl record or collection into json object using json_object(*) function.Is it possible or is there any other way?
As long as they are database types (not solely PLSQL types) you could do it with a table function, eg
SQL> create or replace
2 type the_row as object (
3 x int,
4 y int );
5 /
Type created.
SQL>
SQL> create or replace
2 type the_row_list as table
3 of the_row
4 /
Type created.
SQL>
SQL> -- interestingly, direct SELECT fails
SQL>
SQL> select json_object(*)
2 from table(the_row_list(the_row(1,1),the_row(2,2)));
select json_object(*)
*
ERROR at line 1:
ORA-40579: star expansion is not allowed
SQL> -- but a WITH is ok
SQL> with t as
2 (
3 select *
4 from table(the_row_list(the_row(1,1),the_row(2,2)))
5 )
6 select json_object(*)
7 from t;
JSON_OBJECT(*)
-------------------------------------------------------------------
{"X":1,"Y":1}
{"X":2,"Y":2}
I am trying to create a table function to use in tableau's custom SQL, but I am getting an error, type mismatch found between FETCH cursor and INTO variable. Below is the code I am trying, I have created a type object and table of that type object. Function my_fct should return the table with a select statement output.
CREATE
OR replace type DATA_OBJ AS OBJECT (
id varchar2(10)
);
CREATE
OR replace type
DATA_OBJ_TAB AS TABLE OF DATA_OBJ;
CREATE OR REPLACE FUNCTION my_fct()
RETURN DATA_OBJ_TAB PIPELINED
AS
TYPE CurTyp IS REF CURSOR RETURN DATA_OBJ_TAB%ROWTYPE;
rc CurTyp;
CURSOR data IS SELECT ID from alumni_data;
BEGIN
FOR rc IN data LOOP
PIPE ROW (rc);
END LOOP;
END;
This can be implemented with a packaged PTF without using the SQL data types at all.
Something like this:
create table alumni_data (id, memo) as
select rownum id, 'memo '||rownum from dual connect by level<=3
/
create or replace package pack as
type arrT is table of alumni_data%rowtype;
function get (c varchar2) return arrT pipelined;
end;
/
create or replace package body pack as
function get (c varchar2) return arrT pipelined is
arr arrT;
begin
select * bulk collect into arr
from alumni_data
where memo like c||'%';
for i in 1..arr.count loop
pipe row (arr(i));
end loop;
return;
end;
end;
/
Result:
select * from pack.get ('mem');
ID MEMO
---------- ---------------------------------------------
1 memo 1
2 memo 2
3 memo 3
Have a look at the following example:
SQL> create or replace type data_obj as object
2 (id varchar2(10));
3 /
Type created.
SQL> create or replace type
2 data_obj_tab as table of data_obj;
3 /
Type created.
SQL> create or replace function my_fct
2 return data_obj_tab pipelined
3 as
4 l_vc data_obj := data_obj(null);
5 begin
6 for cur_r in (select id from alumni_data) loop
7 l_vc.id := cur_r.id;
8 pipe row (l_vc);
9 end loop;
10 return;
11 end;
12 /
Function created.
SQL> select * from table(my_fct);
ID
----------
CLARK
KING
MILLER
SQL>
Does anyone know whether it's possible for a PL/SQL procedure (an error-logging one in this case) to get the name of the function/procedure which called it?
Obviously I could pass the name in as a parameter, but it'd be nice to make a system call or something to get the info - it could just return null or something if it wasn't called from a procedure/function.
If there's no method for this that's fine - just curious if it's possible (searches yield nothing).
There is a package called OWA_UTIL (which is not installed by default in older versions of the database). This has a method WHO_CALLED_ME() which returns the OWNER, OBJECT_NAME, LINE_NO and CALLER_TYPE. Note that if the caller is a packaged procedure it will return the PACKAGE name not the procedure name. In this case there is no way of getting the procedure name; this is because the procedure name can be overloaded, so it's not necessarily very useful.
Find out more.
Since 10gR2 there is also the $$PLSQL_UNIT special function; this will also return the OBJECT NAME (i.e. package not packaged procedure).
I found this forum: http://www.orafaq.com/forum/t/60583/0/. It may be what you are looking.
Basically, you can use the Oracle supplied dbms_utility.format_call_stack:
scott#ORA92> CREATE TABLE error_tab
2 (who_am_i VARCHAR2(61),
3 who_called_me VARCHAR2(61),
4 call_stack CLOB)
5 /
Table created.
scott#ORA92>
scott#ORA92> CREATE OR REPLACE PROCEDURE d
2 AS
3 v_num NUMBER;
4 v_owner VARCHAR2(30);
5 v_name VARCHAR2(30);
6 v_line NUMBER;
7 v_caller_t VARCHAR2(100);
8 BEGIN
9 select to_number('a') into v_num from dual; -- cause error for testing
10 EXCEPTION
11 WHEN OTHERS THEN
12 who_called_me (v_owner, v_name, v_line, v_caller_t);
13 INSERT INTO error_tab
14 VALUES (who_am_i,
15 v_owner || '.' || v_name,
16 dbms_utility.format_call_stack);
17 END d;
18 /
Procedure created.
scott#ORA92> SHOW ERRORS
No errors.
scott#ORA92> CREATE OR REPLACE PROCEDURE c
2 AS
3 BEGIN
4 d;
5 END c;
6 /
Procedure created.
scott#ORA92> CREATE OR REPLACE PROCEDURE b
2 AS
3 BEGIN
4 c;
5 END b;
6 /
Procedure created.
scott#ORA92> CREATE OR REPLACE PROCEDURE a
2 AS
3 BEGIN
4 b;
5 END a;
6 /
Procedure created.
scott#ORA92> execute a
PL/SQL procedure successfully completed.
scott#ORA92> COLUMN who_am_i FORMAT A13
scott#ORA92> COLUMN who_called_me FORMAT A13
scott#ORA92> COLUMN call_stack FORMAT A45
scott#ORA92> SELECT * FROM error_tab
2 /
WHO_AM_I WHO_CALLED_ME CALL_STACK
------------- ------------- ---------------------------------------------
SCOTT.D SCOTT.C ----- PL/SQL Call Stack -----
object line object
handle number name
6623F488 1 anonymous block
66292138 13 procedure SCOTT.D
66299430 4 procedure SCOTT.C
6623D2F8 4 procedure SCOTT.B
6624F994 4 procedure SCOTT.A
66299984 1 anonymous block
scott#ORA92>
Basically, all you need to do is to define vars and pass them in a call to a utility method to fill them up with values:
create or replace procedure some_test_proc (p_some_int int)
is
owner_name VARCHAR2 (100);
caller_name VARCHAR2 (100);
line_number NUMBER;
caller_type VARCHAR2 (100);
begin
....
OWA_UTIL.WHO_CALLED_ME (owner_name,caller_name,line_number,caller_type);
-- now you can insert those values along with systimestamp into a log file
....
end;
Is there any way to create an implode routine in PL/SQL that takes any custom datatype as a parameter and concatenates its members, delimited by some specified string?
For example, say I've got the following type:
CREATE TYPE myPerson AS OBJECT(
forename VARCHAR2(50),
surname VARCHAR2(50),
age NUMBER
);
Then, say a function returns an object of type myPerson, but I want the columns concatenated together:
SELECT implode(getPerson(1234),'$$') from dual;
to return (supposing the data in this contrived example is set up):
John$$Doe$$55
Where the delimiter can be specified as an optional parameter, but the type of the first parameter could be anything (not necessarily myPerson).
Your custom datatype can support methods and methods can have parameters.
CREATE TYPE myPerson AS OBJECT(
forename VARCHAR2(50),
surname VARCHAR2(50),
age NUMBER,
MEMBER FUNCTION
get_record(pGlue IN varchar2) RETURN VARCHAR2 );
CREATE TYPE BODY myPerson
AS
MEMBER FUNCTION get_record(pGlue varchar2) RETURN VARCHAR2
IS
BEGIN
RETURN forename || pGlue || surname || pGlue || age ;
END get_record;
END;
It is possible to build a generic way of handling these strings, by using inheritance and polymorphism. If we're going to use objects, we should leverage the capabilities of object-orineted programming.
Firstly we need a root object. This TYPE is not instantiable, which means we cannot actually declare an instance of it. Note that the TO_STRING() member function is also declared as NOT INSTANTIABLE. This means any TYPE which inherits from STRINGABLE_TYPE must have its own implementation of the method.
SQL> create or replace type stringable_type as object
2 ( id number(7,0)
3 , NOT INSTANTIABLE member function to_string
4 return varchar2
5 )
6 not final not instantiable
7 /
Type created.
SQL>
Here is one type which inherits from STRINGABLE_TYPE. The OVERRIDING keyword is mandatory, even though the declartion of the parent type compels us to implement it.
SQL> create or replace type emp_type under stringable_type
2 ( empno number(7,0)
3 , ename varchar2(20)
4 , sal number(7,2)
5 , OVERRIDING member function to_string
6 return varchar2
7 );
8 /
Type created.
SQL> create or replace type body emp_type
2 is
3 OVERRIDING member function to_string
4 return varchar2
5 is
6 begin
7 return 'EMP>>'||self.id||'='||self.empno||'::'||self.ename||'::'||self.sal;
8 end;
9 end;
10 /
Type body created.
SQL>
Here is another type...
SQL> create or replace type dept_type under stringable_type
2 ( deptno number(2,0)
3 , dname varchar2(30)
4 , OVERRIDING member function to_string
5 return varchar2
6 );
7 /
Type created.
SQL> create or replace type body dept_type
2 is
3 OVERRIDING member function to_string
4 return varchar2
5 is
6 begin
7 return 'DEPT>>'||self.id||'='||self.deptno||'::'||self.dname;
8 end;
9 end;
10 /
Type body created.
SQL>
Now, we can create a function which takes the generic type and invokes the generic method:
SQL> create or replace function type_to_string
2 (p_obj in stringable_type)
3 return varchar2
4 is
5 begin
6 return p_obj.to_string();
7 end;
8 /
Function created.
SQL>
Through the wonders of polymorphism we can pass two different objects to the function, which will actually execute the overriding method:
SQL> set serveroutput on
SQL> declare
2 obj1 emp_type;
3 obj2 dept_type;
4 begin
5 obj1 := emp_type(1, 8000, 'VAN WIJK', 3500);
6 obj2 := dept_type(2, 20, 'COMMUNICATIONS');
7 dbms_output.put_line(type_to_string(obj1));
8 dbms_output.put_line(type_to_string(obj2));
9 end;
10 /
EMP>>1=8000::VAN WIJK::3500
DEPT>>2=20::COMMUNICATIONS
PL/SQL procedure successfully completed.
SQL>
It's quite a lot of work to get to this point. It would be neat if Oracle's TYPE at least had an abstract TO_STRING() baked into it, which we could just override. But that is just one of many loose ends in their object implementation 8-)
I'm attempting to create a procedure in Oracle Express Server (Application Express 2.1.0.00.39) using the web interface.
This is the SQL I'm running via the SQL Commands option in the web interface
CREATE OR REPLACE PROCEDURE my_procedure (listOfNumbers num_list,
v_value varchar2)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE my_table
SET my_column = v_value
WHERE my_row_id IN (SELECT column_value
FROM TABLE(listOfNumbers));
COMMIT;
END;
UPDATE:
Changed SELECT column_value FROM TABLE to SELECT column_value FROM TABLE(listOfNumbers) but now I get the following error:
PLS-00201: identifier 'num_list' must
be declared
UPDATE 2:
Here is how I created my type:
CREATE OR REPLACE TYPE "num_list" as table of NUMBER(38,1)
/
Seems the error is being caused on the parameter declaration line:
(listOfNumbers num_list, v_value varchar2)
Below is the object details as displayed by the Oracle Database Express Edition web interface.
Try ...TABLE(CAST(listOfNumbers AS num_list)).
The SQL parser simply sees a bind placeholder in place of listOfNumbers, and since it's a custom type you need to tell it what type it is.
This will only work if num_list has been defined as a type in the schema, not just declared as a type in a PL/SQL block.
Your code works - providing the array type has been declared correctly (see below). As you are still having a problem I suspect that is where you are going wrong. But you need to post the code you are using to create the NUM_LIST type in order for us to correct it.
My test data:
SQL> select * from my_table
2 /
MY_COLUMN MY_ROW_ID
-------------------- ----------
APC 1
XYZ 2
JFK 3
SQL>
In order to use a type in a SQL statement we must create it as a SQL object:
SQL> create type num_list as table of number;
2 /
Type created.
SQL>
SQL>
SQL> create or replace procedure my_procedure
2 (listofnumbers num_list,
3 v_value varchar2)
4 is
5 begin
6
7 update my_table
8 set my_column = v_value
9 where my_row_id in (select column_value
10 from table(listofnumbers));
11
12 end;
13 /
Procedure created.
SQL>
Executing the procedure:
SQL> declare
2 n num_list := num_list(1,3);
3 begin
4 my_procedure (n , 'FOX IN SOCKS');
5 end;
6 /
PL/SQL procedure successfully completed.
SQL>
And lo!
SQL> select * from my_table
2 /
MY_COLUMN MY_ROW_ID
-------------------- ----------
FOX IN SOCKS 1
XYZ 2
FOX IN SOCKS 3
SQL>
Apparently I was creating the type with quotes around the name:
The following didn't work:
CREATE OR REPLACE TYPE "NUMBER_T" as table of NUMBER(38,1)
When I did it without the quotes and then created the procedure, it was able to recognize it.
The following did work:
CREATE OR REPLACE TYPE NUMBER_T as table of NUMBER(38,1)
I'm not sure why, but it worked.