Oracle Inner Join with Collection? - oracle

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;

Related

Use fields from another table as max-number-rows-with-type constrain in oracle

I have two tables:
CREATE TABLE users (
user_id INT(7) NOT NULL,
restricted_type VARCHAR(64) NOT NULL
)
CREATE TABLE type_restrictions (
name VARCHAR(64) NOT NULL,
restriction INT NOT NULL
)
I want to check on insert, that there are no more than restriction users with restricted_type = type_restriction.name.
At this point I'm inserting data with this query:
INSERT INTO users (user_id, restricted_type) SELECT <id>, <type> FROM DUAL
WHERE NOT EXISTS (
SELECT 1
FROM type_restrictions T
WHERE T.name = <type> AND T.restriction < (
SELECT COUNT(*)
FROM users U
WHERE U.user_id = <id> AND U.restricted_type = <type>)
)
But with two or more parallel queries it is possible to end up with more users with restricted_type than actual restriction for this type.
Is there any way to make such constraint work? (Also, I always insert only one row per query, if it helps)
You cannot use select ... in constraint. You cannot select from table which you are inserting into in normal trigger. What you can do? Materialized view (probably, I am not sure) or compound trigger. Here is my (working) try:
create or replace trigger trg_users_restrict
for insert on users compound trigger
type tt is table of number index by varchar2(5);
vt tt;
i varchar2(5);
v_max int;
before statement is
begin
for r in (select restricted_type, count(1) cnt from users group by restricted_type)
loop
vt(r.restricted_type) := r.cnt;
end loop;
end before statement;
after each row is
begin
begin
vt(:new.restricted_type) := vt(:new.restricted_type) + 1;
exception when no_data_found then
vt(:new.restricted_type) := 1;
end;
end after each row;
after statement is
begin
i := vt.first;
while i is not null loop
select nvl(max(restriction), 0) into v_max
from type_restrictions where name = i;
if vt(i) > v_max then
raise_application_error( -20001,
'maximum number exceeded for restriction type ' || i );
end if;
i := vt.next(i);
end loop;
end after statement;
end trg_users_restrict;
In before statement I grouped data from users table into collection. In after each row I increased proper values in collection for newly inserted row(s). In after statement I check if data in collection exceeds allowed ranges in table type_restrictions.
When two sessions insert concurent data then this which commits last causes exception.

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;

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!

Return table from function in Oracle

Following function works with SQL Server.
CREATE FUNCTION GET_EMP_PROB_START_ATT_CODE(#EVAL_TYPE VARCHAR(6))
RETURNS #EMP_PROB_START_ATT_CODE TABLE (
PROBATION_START_ATT_CODE varchar(6) NULL
)
AS
BEGIN
IF #EVAL_TYPE='New'
INSERT INTO #EMP_PROB_START_ATT_CODE (PROBATION_START_ATT_CODE)
SELECT NEW_EMP_PROB_START_ATT_CODE FROM HS_HR_PEA_DYNAMIC_ATTRIBUTES;
ELSE
INSERT INTO #EMP_PROB_START_ATT_CODE (PROBATION_START_ATT_CODE)
SELECT EXIS_EMP_PROB_START_ATT_CODE FROM HS_HR_PEA_DYNAMIC_ATTRIBUTES;
RETURN;
END;
I just want to convert this to Oracle equivalent query . Tried but not succeeded.
CREATE FUNCTION GET_EMP_PROB_START_ATT_CODE(EVAL_TYPE VARCHAR(6))
RETURN EMP_PROB_START_ATT_CODE IS TABLE
PROBATION_START_ATT_CODE VARCHAR(6) NULL;
AS
BEGIN
IF EVAL_TYPE='New'
INSERT INTO EMP_PROB_START_ATT_CODE (PROBATION_START_ATT_CODE)
SELECT NEW_EMP_PROB_START_ATT_CODE FROM HS_HR_PEA_DYNAMIC_ATTRIBUTES;
ELSE
INSERT INTO EMP_PROB_START_ATT_CODE (PROBATION_START_ATT_CODE)
SELECT EXIS_EMP_PROB_START_ATT_CODE FROM HS_HR_PEA_DYNAMIC_ATTRIBUTES;
RETURN;
END;
Warning: compiled but with compilation errors
Any help guys..
First of all, create a database-wide type:
create type string_tab6 is table of varchar2(6);
Then you would create your function like so:
create or replace function get_emp_prob_start_att_code(eval_type varchar2)
return string_tab6
is
v_array string_tab6;
begin
if eval_type = 'New' then
select new_emp_prob_start_att_code
bulk collect into v_array
from hs_hr_pea_dynamic_attributes;
else
select exis_emp_prob_start_att_code
bulk collect into v_array
from hs_hr_pea_dynamic_attributes;
end if;
return v_array;
end get_emp_prob_start_att_code;
/
However, I would question why you need such a function. What are you going to use it for?
In Oracle, I'd expect to see that logic embedded directly into whichever SQL statement needs that info, which you could easily do via a case statement.
First define row type. For existing database table you can use %ROWTYPE.
Then define table type.
Finally use the table type as return type of the function.
TYPE tr_row IS my_table%ROWTYPE;
TYPE tab_row IS TABLE OF tr_row;
FUNCTION get_row RETURN tab_row IS
mytab tab_row;
BEGIN
SELECT * FROM my_table
BULK COLLECT INTO mytab;
RETURN mytab;
END;

How to return a Cursor for pl/sql table

I select data from several tables. Then i need to edit the data returned from the cursor before returning. The cursor will then be passed to a perl script to display the rows.
To that i build a pl/sql table as in the following code. What i need to know is how to return the to that table ?
At present i get the error "table or view doesn't exist". Test code i use for a simple table is attached here.
CREATE OR REPLACE FUNCTION test_rep
RETURN SYS_REFCURSOR
AS
CURSOR rec_Cur IS
SELECT table1.NAME,
table1.ID
FROM TESTREPORT table1;
TYPE rec_Table IS TABLE OF rec_Cur%ROWTYPE INDEX BY PLS_INTEGER;
working_Rec_Table rec_Table;
TYPE n_trade_rec IS RECORD
(
NAME VARCHAR2(15),
ID NUMBER
);
TYPE ga_novated_trades IS TABLE OF n_trade_rec index by VARCHAR2(15);
va_novated_trades ga_novated_trades;
v_unique_key VARCHAR2(15);
TYPE db_cursor IS REF CURSOR;
db_cursor2 db_cursor;
BEGIN
OPEN rec_Cur;
FETCH rec_Cur BULK COLLECT INTO working_Rec_Table;
FOR I IN 1..working_Rec_Table.COUNT LOOP
v_unique_key := working_Rec_Table(I).NAME;
va_novated_trades(v_unique_key).NAME := working_Rec_Table(I).NAME;
va_novated_trades(v_unique_key).ID := working_Rec_Table(I).ID;
END LOOP; --FOR LOOP
OPEN db_cursor2 FOR SELECT * FROM va_novated_trades; --ERROR LINE
CLOSE rec_Cur;
RETURN db_cursor2;
END test_rep;
/
Basically there is a way to select from a table type in oracle using the TABLE() function
SELECT * FROM table(va_novated_trades);
But this works only for schema table types and on plsql tables (table types defined in the SCHEMA and not in a plsql package):
CREATE TYPE n_trade_rec AS OBJECT
(
NAME VARCHAR2(15),
ID NUMBER
);
CREATE TYPE ga_novated_trades AS TABLE OF n_trade_rec;
But I still think you should try to do it all in a query (and/or in the perl script),
For example, there is one field where i have to analyse the 4th
character and then edit other fields accordingly
This can be achieved in the query, could be something like:
select case when substr(one_field, 4, 1) = 'A' then 'A.' || sec_field
when substr(one_field, 4, 1) = 'B' then 'B.' || sec_field
else sec_field
end as new_sec_field,
case when substr(one_field, 4, 1) = 'A' then 100 * trd_field
when substr(one_field, 4, 1) = 'B' then 1000 * trd_field
else trd_field
end as new_trd_field,
-- and so on
from TESTREPORT

Resources