Oracle table function on multi-layer-collection - oracle

I have a need for two Collection types in my program. I've made a simplified example.
type Trec1 is record (Tval1 number);
type Ttab1 is table of Trec1;
type Trec2 is record
(
Tval1 number,
Tval2 Ttab1
);
type Ttab2 is table of Trec2;
This Query Works fine:
select Max (Tval1) from table (Ttab1);
Whereas
select Max (Tval1) from table (Ttab2);
gives me
[Error] PLS-00382 (40: 44): PLS-00382: expression is of wrong type
[Error] ORA-22905 (40: 37): PL/SQL: ORA-22905: cannot access rows from a non-nested table item
I've concluded that the Collection with a nested Collection is what causes the errors. Is there any way to do table functions on this type of Collection, or should i loop through it normally?

To be this works perfectly.
But I have created it as SQL types, not a PL/SQL type.
Added sample code for the same
Case 1:
create type SUBTYP1 as OBJECT
(
AGE NUMBER,
NAME VARCHAR2(10)
);
/
create type TABLETYP1 AS TABLE OF SUBTYP1;
/
DECLARE
ty1 TABLETYP1 := TABLETYP1() ;
v_age number;
v_name varchar2(10);
BEGIn
ty1.extend;
ty1(1) := SUBTYP1(12,'Mahesh');
SELECT * into v_age,v_name from TABLE(ty1);
dbms_output.put_line(v_age||':'||v_name);
END;
/
Case 2:
create or replace type SUBTYP2 as OBJECT
(
AGE NUMBER,
typ1 TABLETYP1
);
/
create type TABLETYP2 AS TABLE OF SUBTYP2;
/
DECLARE
ty2 TABLETYP2 := TABLETYP2() ;
ty1 TABLETYP1 := TABLETYP1() ;
v_age number;
v_name varchar2(10);
BEGIn
ty1.extend;
ty1(1) := SUBTYP1(12,'Mahesh');
ty2.extend;
ty2(1) := SUBTYP2(14,ty1);
SELECT AGE into v_age from TABLE(ty2);
dbms_output.put_line(v_age);
END;
/

What you described are PL/SQL types and they work only in PL/SQL not in SQL. So I doubt that
select Max (Tval1) from table (Ttab1);
works fine. Can you show full creation script and output please?
If you need to have complex types returned by your query you need to create SQL collection (aka nested table) and SQL Object Type (instead of record). They are created with individual CREATE statement and they are persistent objects stored in data dictionary.

Related

PLS-00355 error whike creating a new table type

I'm getting the PLS-00355 error while trying to create the new type like this:
CREATE OR REPLACE TYPE DAYS_T IS TABLE OF VARCHAR(250) INDEX BY BINARY_INTEGER;
Any clue what is wrong?
Many thanks!
This is what you did and how Oracle responded:
SQL> CREATE OR REPLACE TYPE DAYS_T IS TABLE OF VARCHAR(250) INDEX BY BINARY_INTEGER;
2 /
Warning: Type created with compilation errors.
SQL> show err
Errors for TYPE DAYS_T:
LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0 PL/SQL: Compilation unit analysis terminated
1/16 PLS-00355: use of pl/sql table not allowed in this context
On the other hand:
SQL> CREATE OR REPLACE TYPE DAYS_T IS TABLE OF VARCHAR2(250);
2 /
Type created.
SQL>
Responding to your comment: if you declared type at PL/SQL level (not SQL), then your code (without create or replace, though) would be OK (line #2 is what you used, literally):
SQL> declare
2 TYPE DAYS_T IS TABLE OF VARCHAR(250) INDEX BY BINARY_INTEGER;
3 begin
4 null;
5 end;
6 /
PL/SQL procedure successfully completed.
SQL>
You can define a nested table collection type in the SQL scope using:
CREATE OR REPLACE TYPE DAYS_T IS TABLE OF VARCHAR(250);
You can define an associative array collection type in a PL/SQL scope using:
DECLARE
TYPE DAYS_T IS TABLE OF VARCHAR(250) INDEX BY BINARY_INTEGER;
BEGIN
NULL;
END;
/
You could also locally define a nested-table collection type in a PL/SQL scope using:
DECLARE
TYPE DAYS_T IS TABLE OF VARCHAR(250);
BEGIN
NULL;
END;
/
However, you cannot define an associative array collection type in the SQL scope as it is a PL/SQL only data type.
You then asked in comments:
But how can I add an index?
They both have an index.
For example, after declaring the type in SQL, you can use the nested table collection in PL/SQL like this:
DECLARE
v_days DAYS_T;
BEGIN
v_days := DAYS_T(); -- Initialise the collection.
v_days.EXTEND(3); -- Extend the collection by 3 elements.
v_days(1) := 'Monday'; -- Set the first element.
v_days(3) := 'Wednesday'; -- Set the third element.
FOR i IN 1 .. v_days.COUNT LOOP
DBMS_OUTPUT.PUT_LINE( i || ' = ' || v_days(i) );
END LOOP;
END;
/
Which outputs:
1 = Monday
2 =
3 = Wednesday
db<>fiddle here

Oracle Open Cursor for Using gives error ORA-00932: inconsistent datatypes

I have written an Oracle Stored Function where I am trying to read few records from a DB table into a Cursor and set into a Custom Type. But I am getting Error while calling this Function.
Custom object type:
create or replace
TYPE SHARED_ACCOUNT_TYPE AS object(
item_account_id NUMBER(11),
share_by_id NUMBER(11),
share_to_id NUMBER(11)
)
Custom table Type:
create or replace
TYPE SHARED_ACCOUNT_TYPE_COLLECTION
as table of SHARED_ACCOUNT_TYPE
Main Stored Function:
FUNCTION getUserSharedAccounts(v_user_id IN NUMBER)
RETURN SHARED_ACCOUNT_TYPE_COLLECTION
IS
sharedAccounts_query VARCHAR2(200);
sharedAccountCollection SHARED_ACCOUNT_TYPE_COLLECTION;
TYPE SharedAccountCursorType IS REF CURSOR; -- define weak REF CURSOR type
shared_account_cursor SharedAccountCursorType; -- declare cursor variable
BEGIN
shareAccounts_query :=
'SELECT item_id, share_by_id, share_to_id FROM share_accounts WHERE share_to_id = :s';
OPEN shared_account_cursor FOR sharedAccount_query USING v_user_id;
FETCH shared_account_cursor bulk collect into sharedAccountCollection;
CLOSE shared_account_cursor;
RETURN sharedAccountCollection;
END;
I am calling above function like so:
declare
sharedAccount SHARED_ACCOUNT_TYPE_COLLECTION;
begin
sharedAccount := PKG_DATA_POINT_UTIL.getUserSharedAccounts(991033632);
end;
Note: All 3 fields fetched from DB table as of Type NUMBER(11).
Error I got is:
ORA-00932: inconsistent datatypes: expected %s got %s
ORA-06512: at"APP.PKG_DATA_HELPER_UTIL", line 1403
Ignoring the inconsistent naming that prevents this from compiling at all, the error is coming from the fetch, not the open.
The issue here is that you have a target collection of objects but you're selecting scalar values - Oracle can't (or won't) convert them to your object type automagically. So you need to create the object as part of the query, i.e instead of
SELECT item_id, share_by_id, share_to_id
you need
SELECT SHARED_ACCOUNT_TYPE(item_id, share_by_id, share_to_id)
so:
sharedAccounts_query :=
'SELECT SHARED_ACCOUNT_TYPE(item_id, share_by_id, share_to_id) FROM share_accounts WHERE share_to_id = :s';
Incidentally, you don't need to use dynamic SQL or a cursor here; static SQL would be fine:
FUNCTION getUserSharedAccounts(v_user_id IN NUMBER)
RETURN SHARED_ACCOUNT_TYPE_COLLECTION
IS
sharedAccountCollection SHARED_ACCOUNT_TYPE_COLLECTION;
BEGIN
SELECT SHARED_ACCOUNT_TYPE(item_id, share_by_id, share_to_id)
bulk collect into sharedAccountCollection
FROM share_accounts
WHERE share_to_id = v_user_id;
RETURN sharedAccountCollection;
END;

How to reference individual element of funtion that returns table type in oracle

I have created a function which return table type i.e two column values.I want to use these individual columns while inserting into other table via select statement.
create or replace type emp_sal as object(empno number,sal number);
create or replace nt_emp_sal is table of emp_sal;
create or replace function emp_fun(deptno number)
return nt_emp_sal
is
l_nt_emp_sal nt_emp_sal := nt_emp_sal();
begin
select emp_sal(empno,sal) bulk collect
into l_nt_emp_sal
from emp where deptno=p_deptno;
return l_nt_emp_sal;
end;
You basically need to use the TABLE() keyword in your query around the function call to use the result just like any other table. Given your sample code, you will write something like :
INSERT INTO ... (colA, colB)
SELECT * FROM TABLE(emp_fun(123))
-- ^^^^^^ ^
From the doc "Using Pipelined and Parallel Table Functions":
Table functions return a collection type instance and can be queried like a table by calling the function in the FROM clause of a query. Table functions use the TABLE keyword.
You can find a detailed example at http://oracle-base.com/articles/misc/pipelined-table-functions.php :
CREATE TYPE t_tf_row AS OBJECT (
id NUMBER,
description VARCHAR2(50)
);
/
CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
/
-- Build the table function itself.
CREATE OR REPLACE FUNCTION get_tab_tf (p_rows IN NUMBER)
RETURN t_tf_tab
AS
l_tab t_tf_tab := t_tf_tab();
BEGIN
-- func def here
-- ...
END;
/
And here is the relevant part:
SELECT *
FROM TABLE(get_tab_tf(10))
-- ^^^^^^ ^
ORDER BY id DESC

Create table-returning function in package, using %ROWTYPE and TABLE OF

I have some tables which I cannot change. For each table, I need to create a FUNCTION inside a PACKAGE that returns a temporary table with the same data/layout as the original table. Instead of copying all table column definitions manually, I'd like to use statements such as %ROWTYPE.
I want to tell Oracle: "this function returns a table with the same layout as the original table XY".
Please have a look at this example. This is the (legacy) table:
CREATE TABLE TEST.Emp (
ID RAW(16),
NAME VARCHAR2(10)
);
/
These are the package and type definitions:
CREATE OR REPLACE TYPE TEST.row_Emp AS OBJECT (
ID RAW(16),
NAME VARCHAR2(10)
);
/
CREATE OR REPLACE PACKAGE TEST.Emp_PKG AS
TYPE t_Emp IS TABLE OF TEST.row_Emp INDEX BY BINARY_INTEGER;
FUNCTION F_Emp_Select (
VersionId INT DEFAULT NULL
) RETURN t_Emp;
END;
/
And here is the package body:
CREATE OR REPLACE PACKAGE BODY TEST.Emp_PKG AS
FUNCTION F_Emp_Select (
VersionId INT DEFAULT NULL
) RETURN t_Emp
AS
VersionVar INT := VersionId;
v_ret t_Emp;
BEGIN
SELECT
CAST(
MULTISET(
SELECT ID, NAME FROM TEST.Emp
) AS t_Emp) -- <== this is line 15
INTO v_ret
FROM dual;
RETURN v_ret;
END;
END;
/
If I execute this in SQLPlus, I get the following error:
Errors for PACKAGE BODY TEST.EMP_PKG:
LINE/COL ERROR
-------- -----------------------------------------------------------------
11/9 PL/SQL: SQL Statement ignored
15/22 PL/SQL: ORA-00902: invalid datatype
What am I doing wrong?
EDIT: I need this for a more complex case. This function will be used in other functions and procedures. For example, I need to be able to do a join on the result of the function:
SELECT ... FROM ...
INNER JOIN TEST.Emp_PKG.F_Emp_Select(...) ON ...
So I don't need the whole result.
Sorry for the confusion, I'm coming from SQL Server, where I have done such things many times.
If you need the result as something you can treat as a table, you could use a pipeline function:
CREATE OR REPLACE TYPE row_Emp AS OBJECT (
ID RAW(16),
NAME VARCHAR2(10)
);
/
CREATE OR REPLACE TYPE tab_Emp AS TABLE OF row_Emp
/
CREATE OR REPLACE PACKAGE Emp_PKG AS
FUNCTION F_Emp_Select (
VersionId INT DEFAULT NULL
) RETURN tab_Emp PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY Emp_PKG AS
FUNCTION F_Emp_Select (
VersionId INT DEFAULT NULL
) RETURN tab_EMP PIPELINED
AS
BEGIN
FOR row IN (SELECT ID, NAME FROM Emp) LOOP
PIPE ROW (row_Emp(row.ID, row.NAME));
END LOOP;
RETURN;
END;
END;
/
Notice the object type and the table type have to be declared at schema level; the table type can't be a PL/SQL-seclared table (collection) type as that wouldn't be usable in plain SQL, even within other PL/SQL. Unfortunately %ROWTYPE is a PL/SQL construct, so you can't use that to define your schema-level table type.
You can then do:
SELECT * FROM TABLE(Emp_PKG.F_Emp_Select(<optional versionId>));
... or use it in a join as:
SELECT ... FROM ...
INNER JOIN TABLE(TEST.Emp_PKG.F_Emp_Select(...)) ON ...
SQL FIddle demo.
Assuming that your goal is to return a collection, not a temporary table, it's a bit less complicated than your example
CREATE OR REPLACE PACKAGE emp_pkg
AS
TYPE emp_typ IS TABLE OF emp%rowtype index by binary_integer;
FUNCTION get_emps
RETURN emp_typ;
END;
CREATE OR REPLACE PACKAGE BODY emp_pkg
AS
FUNCTION get_emps
RETURN emp_typ
IS
l_emps emp_typ;
BEGIN
SELECT *
BULK COLLECT INTO l_emps
FROM emp;
RETURN l_emps;
END;
END;
Now, architecturally, I would be very concerned about a solution that involved selecting all the data from a table into a PL/SQL collection. PL/SQL collections have to be stored entirely in the session's SGA which is relatively expensive server RAM. If you have thousands or tens of thousands of rows in your table, that can be a pretty substantial amount of space on the server particularly if there may be many different sessions all calling these procedures at roughly the same time. If your tables all have a couple hundred rows and only one session at a time will be using these functions, maybe this approach will be sufficient.

How to populate an array in an oracle stored procedure?

How to use array( Varray) in store procedure. Actually,i have make a stored procedure from which i retrieve a list of elements.
For example:
create or replace procedure GetTargetFields ( fileformat in varchar2,
filefields out Varray(4) )
IS
BEGIN
SELECT id
INTO filefields
FROM tablename;
END;
use BULK COLLECT INTO:
SQL> CREATE OR REPLACE TYPE vrray_4 AS VARRAY(4) OF VARCHAR2(10);
2 /
Type created
SQL> CREATE OR REPLACE PROCEDURE GetTargetFields(fileformat IN VARCHAR2,
2 filefields OUT vrray_4) IS
3 BEGIN
4 SELECT dummy BULK COLLECT INTO filefields FROM dual;
5 END;
6 /
Procedure created
SQL> DECLARE
2 x vrray_4;
3 BEGIN
4 GetTargetFields(NULL, x);
5 END;
6 /
PL/SQL procedure successfully completed
Also make sure that your query doesn't return more than 4 rows (for a VARRAY(4)) or you will run into ORA-22165
Niraj. You should use the principles Vincent provided, but I suggest you use nested table type instead of varray in case you don't need exactly varray type in your logic. This will save you from ORA-22165 error if the query returns more then 4 rows - nested tabled will be automatically expanded to the size needed. You define nested table type as follows:
declare
type TStrTab is table of varchar2(10);
fStrTab TStrTab := TStrTab();
begin
select ... bulk collect into fStrTab from...
end;
More information about PL/SQL collection types can be found in official Oracle PL-SQL User's Guide and Reference Chapter 5.
Two things:
You need to declare a named type -- you can't use VARRAY directly in a parameter declaration. (Unless this has changed in 11g.)
You need to use BULK COLLECT to use a single query to populate a collection.
Example:
CREATE TYPE fieldlist AS VARRAY(4) OF NUMBER;
CREATE PROCEDURE GetTargetFields( filefields OUT fieldlist )
AS
BEGIN
SELECT id BULK COLLECT INTO filefields FROM tablename;
END;

Resources