Oracle PL/SQL - Show results of declared table - oracle

I am using Toad. I have a declaration of a table in a package as follows:
TYPE MyRecordType IS RECORD
(ID MyTable.ID%TYPE
,FIELD1 MyTable.FIELD1%TYPE
,FIELD2 MyTable.FIELD2%TYPE
,FIELD3 MyTable.FIELD3%TYPE
,ANOTHERFIELD VARCHAR2(80)
);
TYPE MyTableType IS TABLE OF MyRecordType INDEX BY BINARY_INTEGER;
There is a procedure (lets say MyProcedure), that is using an object of this table type as input/output. I want to run the procedure and see the results (how the table is filled). So I am thinking I will select the results from the table:
declare
IO_table MyPackage.MyTableType;
begin
MyPackage.MyProcedure (IO_table
,parameter1
,parameter2
,parameter3);
select * from IO_table;
end;
I get the message:
Table or view does not exist (for IO_table). If I remove the select line, the procedure runs successfully, but I cannot see its results. How can I see the contents of IO_table after I call the procedure?

You cannot see the results for a PL/SQL table by using Select * from IO_table
You will need to loop through the collection in the annonymous block.
do something like, given in pseudo code below...
declare
IO_table MyPackage.MyTableType;
l_index BINARY_INTEGER;
begin
MyPackage.MyProcedure (IO_table
,parameter1
,parameter2
,parameter3);
l_index := IO_table.first;
While l_index is not null
loop
dbms_output.put_line (IO_table(l_index).id);
.
.
.
.
l_index :=IO_table.next(l_index_id);
end loop;
end;

You have to do it like this:
select * from TABLE(IO_table);
and, of course you missed the INTO or BULK COLLECT INTO clause

1) You can not use associated arrays in SELECT statement, Just nested tables or varrays declared globally.
2) You should use TABLE() expression in SELECT statement
3) You can't simply use SELECT in PL/SQL code - cursor FOR LOOP or REF CURSOR or BULK COLLECT INTO or INTO must be used.
4) The last but not least - please study the manual:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28371/adobjcol.htm#ADOBJ00204
Just an example:
SQL> create type t_obj as object( id int, name varchar2(10));
2 /
SQL> create type t_obj_tab as table of t_obj;
2 /
SQL> var rc refcursor
SQL> declare
2 t_var t_obj_tab := t_obj_tab();
3 begin
4 t_var.extend(2);
5 t_var(1) := t_obj(1,'A');
6 t_var(2) := t_obj(2,'B');
7 open :rc for select * from table(t_var);
8 end;
9 /
SQL> print rc
ID NAME
---------- ----------
1 A
2 B

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

Unable to create table function in oracle, type mismatch found between FETCH cursor and INTO variable

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>

Oracle PL/SQL 6504 with BULK COLLECT

I have this simple query:
SELECT MEASURE_ID, MEASURE_VALUE FROM MY_TABLE;
At the moment returning just a couple of records (in the future there will plenty of them):
8 265.7
7 559.6
A DESC on such table provides:
Name Null Type
------------ -------- ------------
MEASURE_ID NOT NULL NUMBER
MEASURE_VALUE NUMBER(10,1)
Then I defined the proper PL/SQL types:
CREATE OR REPLACE TYPE HASHMAP_NUM_TYPE_OBJ AS OBJECT (
THE_ID NUMBER,
THE_VALUE NUMBER(10,1)
);
CREATE OR REPLACE TYPE HASHMAP_NUM_TYPE IS TABLE OF HASHMAP_NUM_TYPE_OBJ;
And tried to fetch the records using a BULK COLLECT:
stats_by_measure HASHMAP_NUM_TYPE;
...
OPEN cursor_1 FOR
SELECT MEASURE_ID, MEASURE_VALUE
FROM MY_TABLE;
...
FETCH cursor_1 BULK COLLECT INTO stats_by_measure;
...
CLOSE cursor_1;
But I have the Oracle -6504 error. What am I doing wrong?
Remark: If I fetch the same cursor row by row, using a codeblock like this:
foo NUMBER;
faa NUMBER(10,1);
my_obj HASHMAP_NUM_TYPE_OBJ;
...
LOOP
FETCH cursor_1 INTO foo, faa;
my_obj := HASHMAP_NUM_TYPE_OBJ(foo,faa);
EXIT WHEN cursor_1%NOTFOUND;
END LOOP;
everything works fine!
modify your cursor query like below so that it will have the same type
OPEN cursor_1 FOR
SELECT HASHMAP_NUM_TYPE_OBJ(MEASURE_ID, MEASURE_VALUE)
FROM MY_TABLE;
You can only BULK COLLECT objects into a table of objects. In your case:
SQL> CREATE OR REPLACE TYPE hashmap_num_type_obj AS OBJECT (
2 the_id NUMBER,
3 the_value NUMBER(10,1)
4 );
5 /
Type created
SQL> CREATE OR REPLACE TYPE hashmap_num_type IS TABLE OF hashmap_num_type_obj;
2 /
Type created
SQL> DECLARE
2 l_tab hashmap_num_type;
3 BEGIN
4 SELECT hashmap_num_type_obj(measure_id, measure_value)
5 BULK COLLECT INTO l_tab
6 FROM my_table;
7 END;
8 /
PL/SQL procedure successfully completed
I've solve your question
declare
type REC_TYPE is record (
THE_ID number,
THE_VALUE number
);
type TB_TYPE is table of REC_TYPE index by binary_integer;
TBL TB_TYPE;
cursor CURSOR_1 is
select a1.MEASURE_ID A$1, a1.MEASURE_VALUE A$2
from MY_TABLE a1;
type REF_CUR_ is ref cursor return CURSOR_1%rowtype;
CURSOR_2 REF_CUR_;
begin
open CURSOR_2 for
select a1.MEASURE_ID A$1, a1.MEASURE_VALUE A$2
from MY_TABLE a1;
fetch CURSOR_2 bulk collect into TBL ;
close CURSOR_2;
return;
end;
It's works.
I have found another way without ref cursor there (look for FETCH Statement with BULK COLLECT Clause)
You should retrieve the rows into a type based on a record type rather than an object type. The following works;
DECLARE
TYPE hashmap_num_type_rt IS RECORD
(THE_ID NUMBER,
THE_VALUE NUMBER(10,1)
);
TYPE hashmap_num_type_t IS TABLE OF hashmap_num_type_rt;
stats_by_measure hashmap_num_type_t;
BEGIN
SELECT measure_id, measure_value
BULK COLLECT INTO stats_by_measure
FROM my_table;
FOR i IN 1..stats_by_measure.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE('RECORD '||TO_CHAR(i)||' : ID - '||stats_by_measure(i).the_id||' MeasureVal: '||TO_CHAR(stats_by_measure(i).the_value));
END LOOP;
END;
You could also define a cursor and the create a table type based on the type of the cursor (which is of course is still a row type rather than an object type).
If you want to use a cursor as the row type then try the following;
DECLARE
CURSOR c_measures IS
SELECT measure_id, measure_value
FROM my_table;
TYPE hashmap_num_type_t IS TABLE OF c_measures%ROWTYPE;
stats_by_measure hashmap_num_type_t;
BEGIN
OPEN c_measures;
FETCH c_measures
BULK COLLECT INTO stats_by_measure;
CLOSE c_measures;
FOR i IN 1..stats_by_measure.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE('RECORD '||TO_CHAR(i)||' : ID - '||stats_by_measure(i).measure_id||' MeasureVal: '||TO_CHAR(stats_by_measure(i).measure_value));
END LOOP;
END;

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;

Oracle: Select From Record Datatype

I have a function that returns a record datatype (2 fields: ID and Name). How can I get at the data from a select statement?
Specifically, I am trying using an OracleCommand object attempting to get the object into my C# code. I initially tried ...
CALL FUNCTION_NAME() INTO :loRetVal
... but I get a data type error for whatever type I use. I have also tried ...
SELECT * FROM FUNCTION_NAME()
... and ...
SELECT * FROM TABLE ( FUNCTION_NAME() )
... to no avail. I guess I am looking for ...
SELECT * FROM RECORD ( FUNCTION_NAME() )
... which, of course, doesn't exist.
The only solution I have been able to come up with is to wrap this function call in another function call in which the outer function returns a TABLE of records containing this sole record. This, however, seems cumbersome and I am looking for a simpler method. Any help would be appreciated.
EDIT: Sorry, I have also tried SELECT FUNCTION_NAME() FROM DUAL.
A record datatype is a PL/SQL datatype. SQL doesn't know about it. That's probably why you are getting an error. See this example:
SQL> create package mypkg
2 as
3 type myrec is record
4 ( id int
5 , name varchar2(10)
6 );
7 function f return myrec;
8 end mypkg;
9 /
Package created.
SQL> create package body mypkg
2 as
3 function f return myrec
4 is
5 r myrec;
6 begin
7 r.id := 1;
8 r.name := 'test';
9 return r;
10 end f;
11 end mypkg;
12 /
Package body created.
SQL> desc mypkg
FUNCTION F RETURNS RECORD
ID NUMBER(38) OUT
NAME VARCHAR2(10) OUT
SQL> select mypkg.f from dual
2 /
select mypkg.f from dual
*
ERROR at line 1:
ORA-00902: invalid datatype
The error in SQL I was referring to.
You can call it from PL/SQL though:
SQL> declare
2 r mypkg.myrec;
3 begin
4 r := mypkg.f;
5 dbms_output.put_line(r.id);
6 dbms_output.put_line(r.name);
7 end;
8 /
1
test
PL/SQL procedure successfully completed.
If you want to use the function in SQL, then you can create a SQL objecttype. Note that calling your function directly from C# looks way more preferable than insisting on using SQL to do this. But just for the record:
SQL> drop package mypkg
2 /
Package dropped.
SQL> create type myobj is object
2 ( id int
3 , name varchar2(10)
4 );
5 /
Type created.
SQL> create package mypkg
2 as
3 function f return myobj;
4 end mypkg;
5 /
Package created.
SQL> create package body mypkg
2 as
3 function f return myobj
4 is
5 begin
6 return myobj(1,'test');
7 end f;
8 end mypkg;
9 /
Package body created.
SQL> select mypkg.f from dual
2 /
F(ID, NAME)
--------------------------------------------------------------
MYOBJ(1, 'test')
1 row selected.
Regards,
Rob.
I think this what you are looking for; to get the values out in a select statement:
select result.id as id, result.name
from ( select function() as result from dual);
Because your function returns a record an not a native type you can't use the standard methods. The if you want to get the actual record as an object into C# then you have do some reading on user defined types in the ODP .net documentation.
You could also wrap the function in another function that returns a ref cursor and that is used in C# in a more standard fashion.
Can you
CREATE TYPE <object name> AS TABLE OF <record type>
and use that directly in a SQL statement? I ask because I have a stored proc that I can not edit. The stored proc has an output variable that is record type that I have to reference in a SQL statement. I have already created a function to call the proc, but if I don't have to convert the record to type object that would be nice.
I would later call it like:
SELECT *
FROM TABLE( CAST( <function name>() as <object name>));
The formatting of my comment for Rob van Wijk is bad. To continue his thought.
-- create a collection type
CREATE TYPE myobj_tab AS TABLE OF myobj;
-- have the function return a collection type
CREATE OR REPLACE function f return myobj_tab
IS
objtab myobj_tab;
BEGIN
objtab := myobj_tab(myobj(1,'test'));
return objtab;
end f;
-- CAST it as a table and straight up select from it.
SELECT id, name FROM TABLE(CAST(f() AS myobj_tab));
I think you are looking for PIPELINED functionality:
CREATE TABLE test_table(tt_id INTEGER,tt_text VARCHAR2(40));
CREATE PACKAGE test_pkg IS
TYPE tp_rec IS RECORD(tt_id INTEGER,tt_text VARCHAR2(40));
TYPE tp_recs IS TABLE OF tp_rec;
FUNCTION test_func RETURN tp_recs PIPELINED;
FUNCTION test_func1 RETURN tp_recs PIPELINED;
FUNCTION test_func2(ivar INTEGER) RETURN tp_recs PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY test_pkg IS
FUNCTION test_func RETURN tp_recs PIPELINED
AS
currec tp_rec;
BEGIN
currec.tt_id := 1;
currec.tt_text := 'test1';
PIPE ROW(currec);
END;
FUNCTION test_func1 RETURN tp_recs PIPELINED
AS
currec tp_rec;
CURSOR t_cursor IS
SELECT * FROM test_table;
BEGIN
OPEN t_cursor;
LOOP
FETCH t_cursor INTO currec;
EXIT WHEN t_cursor%NOTFOUND;
PIPE ROW(currec);
END LOOP;
CLOSE t_cursor;
END;
FUNCTION test_func2(ivar INTEGER) RETURN tp_recs PIPELINED
AS
currec tp_rec;
BEGIN
SELECT * INTO currec FROM test_table WHERE tt_id = ivar;
PIPE ROW(currec);
END;
END;
/
BEGIN
INSERT INTO test_table VALUES(1,'test1');
INSERT INTO test_table VALUES(2,'test2');
INSERT INTO test_table VALUES(3,'test3');
COMMIT;
END;
/
SELECT * FROM TABLE(test_pkg.test_func());
SELECT * FROM TABLE(test_pkg.test_func1());
SELECT * FROM TABLE(test_pkg.test_func2(2));
The above code is tested, and should give you a good start on this. Just look up the PIPELINED keyword in Oracle for more information (assuming you are working with Oracle...)
Why do you need to use SQL at all? You might be able to just use a System.Data.CommandType.StoredProcedure to call the function.
call-an-oracle-function-from-c#
Call Oracle Function in package with C#

Resources