Oracle PL/SQL 6504 with BULK COLLECT - oracle

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;

Related

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>

Using %ROWTYPE in Procedure in Oracle

I am trying to use %ROWTYPE in my code and trying to insert value into it using a cursor for loop as below :
CREATE or REPLACE PROCEDURE test_acr (
PROJECT_START_DATE IN DATE,USER_ID IN VARCHAR2)
IS TYPE acr_new IS TABLE OF acr_projected_new%ROWTYPE
INDEX BY SIMPLE_INTEGER;
acr_projected_neww acr_new;
CURSOR WEEKENDING_DATE IS
SELECT WEEKEND_DATE
FROM weekending_table
WHERE WEEKEND_DATE BETWEEN PROJECT_START_DATE AND sysdate;
BEGIN
FOR WEEKEND_DATE_REC in WEEKENDING_DATE LOOP
INSERT INTO acr_projected_neww(WEEKEND_DATE,USERID,TIMESTAMP,ACR_PROJECTED,artificial_id)
SELECT WEEKEND_DATE_REC.WEEKEND_DATE,USER_ID,sysdate,
(select sum(acr_h.activity_impact)
FROM ACR_HISTORY acr_h
LEFT JOIN Activity act on act.activity_id = acr_h.activity_id
LEFT JOIN Activity_Date_Duration act_d on act_d.activity_id = act.activity_id),1 from dual;
END LOOP;
END test_acr;
When i try to run this i get below error:
Error(54,14): PL/SQL: ORA-00942: table or view does not exist
My Requirement is to create virtual table and insert the data into it using cursor for loop if not then any other means is appreciated.
Please help it will be greatly appreciated!
Looks like table name is incorrect in your INSERT statement.
You need not use two queries. Instead, define your cursor such that it has all the columns of the records you want to store in the collection. Then use BULK COLLECT INTO instead of insert as shown. Define your collection as table of cursor%ROWTYPE.
CREATE OR REPLACE PROCEDURE test_acr
IS
CURSOR WEEKENDING_DATE
IS
SELECT a.col1,a.col2,b.col1,b.col2 ,c.col1
from table1 a , table2 b LEFT JOIN table3 c; --Here include all the data from the required tables.
TYPE acr_new
IS
TABLE OF WEEKENDING_DATE%ROWTYPE;
acr_projected_neww acr_new;
BEGIN
FETCH WEEKENDING_DATE BULK COLLECT INTO acr_projected_neww;
END test_acr;
If you need to manipulate your data (access each row - then see script below, this is sequential access (inserts) into nested table (PL/SQL collection)
CREATE or REPLACE PROCEDURE test_acr (PROJECT_START_DATE IN DATE,USER_ID IN VARCHAR2)
IS
TYPE acr_new
IS TABLE OF acr_projected_new%ROWTYPE; // nested table, notice absence of INDEX by clause
acr_projected_neww acr_new := acr_projected_neww(); // instantiation, constructor call
CURSOR WEEKENDING_DATE IS
SELECT WEEKEND_DATE
FROM weekending_table
WHERE WEEKEND_DATE BETWEEN PROJECT_START_DATE AND sysdate;
BEGIN
FOR WEEKEND_DATE_REC in WEEKENDING_DATE
LOOP
acr_new.extend; // make room for the next element in collection
acr_new(acr_new.last) := WEEKEND_DATE_REC; // Adding seq. to the end of collection
...
END LOOP;
END test_acr;
However if you want to BULK INSERT (there is no requirement to get access to each row) see script below
CREATE or REPLACE PROCEDURE test_acr (PROJECT_START_DATE IN DATE,USER_ID IN VARCHAR2)
IS
TYPE acr_new IS TABLE OF acr_projected_new%ROWTYPE; // no INDEX BY clause
acr_projected_neww acr_new = acr_new(); // Notice constructor call
CURSOR WEEKENDING_DATE IS
SELECT WEEKEND_DATE
FROM weekending_table
WHERE WEEKEND_DATE BETWEEN PROJECT_START_DATE AND sysdate;
BEGIN
FETCH WEEKENDING_DATE BULK COLLECT INTO acr_projected_neww;
...
END LOOP;
END test_acr;
I have used temporary table outside my procedure:
CREATE GLOBAL TEMPORARY TABLE "MY_TEMP"
( "WEEKEND_DATE" DATE,
"USERID" VARCHAR2(255 BYTE),
"TIMESTAMP" TIMESTAMP (6),
"ACR_PROJECTED" NUMBER,
"ARTIFICIAL_ID" NUMBER
) ON COMMIT PRESERVE ROWS ;
i have just used the above temporary table inside my Procedure
create or replace PROCEDURE GET_ACR_TEST(
PROJECT_START_DATE IN DATE ,
USER_ID IN VARCHAR2,
) AS
CURSOR WEEKENDING_DATE IS
SELECT WEEKEND_DATE, DURATION
FROM weekending_table where WEEKEND_DATE between PROJECT_START_DATE and sysdate;
Begin
FOR WEEKEND_DATE_REC in WEEKENDING_DATE
LOOP
insert into MY_TEMP (WEEKEND_DATE,USERID,TIMESTAMP,ACR_PROJECTED,artificial_id)
select WEEKEND_DATE_REC.WEEKEND_DATE,USER_ID,sysdate,
(select sum(acr_h.activity_impact)
from ACR_HISTORY acr_h
LEFT JOIN Activity act on act.activity_id = acr_h.activity_id
LEFT JOIN Activity_Date_Duration act_d on act_d.activity_id = act.activity_id),1
from dual;
End Loop;
END GET_ACR_TEST;
The above method is working.
Thank you all for your comments!

Oracle PL/SQL - Show results of declared table

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

Oracle Inner Join with Collection?

I want to join collection of table type with other tables in following example -
I have a function F_GetPendingFeeds which returns the table collection of type feed_log.
I want to join this returning collection with one of the table -
CREATE OR REPLACE PACKAGE BODY L_DemoPkg
IS
TYPE t_feedLog IS TABLE OF feed_log%ROWTYPE
INDEX BY PLS_INTEGER;
FUNCTION F_GetPendingFeeds
RETURN t_feedLog
IS
lo_feedLog t_feedLog;
BEGIN
SELECT feed_log_seq
, processed_dt
, processed_by
, create_dt
, created_by
BULK COLLECT INTO lo_feedLog
FROM feed_log
WHERE status_cd = 0;
RETURN lo_feedLog;
EXCEPTION
WHEN OTHERS THEN
--TODO: Log Exception
RAISE;
END F_GetPendingFeeds;
PROCEDURE P_ProcessFeed
IS
o_pendingFeed t_feedLog;
ln_totalRecords t_feedLog;
BEGIN
-- Get the list of pending feed ids
o_pendingFeed := F_GetPendingFeeds();
-- Check if new data is present for processing
IF o_pendingFeed.COUNT = 0 THEN
dbms_output.put_line('Feed processing failed. No data found.');
RETURN;
END IF;
SELECT COUNT(*)
INTO ln_totalRecords
FROM feed_details t1
, table(o_pendingFeed) t2 --ERROR: ORA-22905: cannot access rows from a non-nested table item
WHERE t1.feed_log_seq = t2.feed_log_seq;
EXCEPTION
WHEN OTHERS THEN
--TODO: Log Exception
RAISE;
END P_ProcessFeed;
END;
I am receiving error as -
PL/SQL: SQL Statement ignored
PL/SQL: ORA-22905: cannot access rows from a non-nested table
item
Please notice that I want to join collection with table -
FROM feed_details t1
, table(o_pendingFeed) t2 --ERROR: ORA-22905: cannot access rows from a non-nested table item WHERE t1.feed_log_seq = t2.feed_log_seq;
Prior to Oracle 12C you could only select from collections that have been created on the server using CREATE TYPE e.g.
SQL> CREATE TYPE r_feedLog IS OBJECT (foo NUMBER, bar VARCHAR2(20));
SQL> CREATE TYPE t_feedLog IS TABLE OF r_feedLog;
Then remove the declaration of t_feedLog from your package.
With Oracle 12C it is possible to select from PL/SQL tables defined in a package specification.
you have several errors here. Firstly, to access an array in a TABLE cast you need to use a SQL array (well, you could use a PL/SQL table still, but that only works for a pipelined function, as Oracle will create the SQL types silently for you; but even in that case its still neater to use a SQL array). so you'd need to do:
SQL> create type r_feedlog is object
2 (
3 feed_log_seq number,
4 processed_dt date,
5 processed_by varchar2(10),
6 create_dt date,
7 created_by varchar2(10)
8 );
9 /
Type created.
SQL> create type t_feedLog as table of r_feedlog;
2 /
Type created.
and then use that and NOT a pl/sql index-by table. secondly
ln_totalRecords t_feedLog;
should be a number not a collection as your selecting count(*) into it. Also :
BULK COLLECT INTO lo_transferFeedDef
should be
BULK COLLECT INTO lo_feedLog
you could have the function as pipelined of course i.e. something like:
CREATE OR REPLACE PACKAGE L_DemoPkg
as
type r_feedlog is record(feed_log_seq number,
processed_dt date,
processed_by varchar2(10),
create_dt date,
created_by varchar2(10));
type t_feedLog is table of r_feedlog;
function F_GetPendingFeeds return t_feedLog pipelined;
procedure P_ProcessFeed;
end;
/
and within the package body:
FUNCTION F_GetPendingFeeds
RETURN t_feedLog pipelined
IS
lo_feedLog r_feedlog;
BEGIN
for r_row in (SELECT feed_log_seq
, processed_dt
, processed_by
, create_dt
, created_by
FROM feed_log
WHERE status_cd = 0)
loop
lo_feedLog.feed_log_seq := r_row.feed_log_seq;
lo_feedLog.processed_dt := r_row.processed_dt;
lo_feedLog.processed_by := r_row.processed_by;
lo_feedLog.create_dt := r_row.create_dt;
lo_feedLog.created_by := r_row.created_by;
pipe row(lo_feedLog);
end loop;
END F_GetPendingFeeds;
within the procedure you could then just:
SELECT COUNT(*)
INTO ln_totalRecords
FROM feed_details t1
, table(F_GetPendingFeeds()) t2
WHERE t1.feed_log_seq = t2.feed_log_seq;
The above was keeping a pl/sql array. if you had the SQL array, the function would be a bit more compact:
FUNCTION F_GetPendingFeeds
RETURN t_feedLog pipelined
IS
BEGIN
for r_row in (SELECT r_feedlog(feed_log_seq
, processed_dt
, processed_by
, create_dt
, created_by) data
FROM feed_log
WHERE status_cd = 0)
loop
pipe row(r_row.data);
end loop;
END F_GetPendingFeeds;

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