Using bulk collect with two rowtypes - oracle

I have a cursor query which returns me record of two tables like below.
cursor c1 is
select teb.*,te.*
from table1 teb, table2 te
where te.col1=teb.col2;
How should I collect them in Collections using bulk collect?
I tried:
type tab_rec is table of table1%rowtype index by pls_integer;
var_rec tab_rec;
type tab_rec1 is table of table2%rowtype index by pls_integer;
var_rec1 tab_rec1;
begin
open c1;
fetch c1 bulk collect into var_rec,ver_rec1;
close c1;
But above doesn't seem to be working.

Reading the fine manual: %ROWTYPE Attribute
The %ROWTYPE attribute lets you declare a record variable that represents either a full or partial row of a database table or view. For every column of the full or partial row, the record has a field with the same name and data type. If the structure of the row changes, then the structure of the record changes accordingly.
The %rowtype attribute works also with cursors:
-- c1 is a valid cursor
v_rec c1%rowtype;
fetch c1 bulk collect into v_rec;
See also this example from Oracle documentation.

Related

How to store a select sql result record in list in Oracle

I have a table employee. below query return one record, want to store it in a list so that latter on i can iterate it and fetch the value.
select employee_name,employee_surname from employee where emp_id = '123';
something like
select employee_name,employee_surname bulk collect into list_emp from employee where emp_id = '123';
.
.
FOR indx IN 1 .. list_emp.count
LOOP
<other sqls>
END LOOP;
This can be done by declaring a package level collection of record.
CREATE OR REPLACE PACKAGE pkg_emp_rec AS
TYPE emp_rec IS RECORD ( employee_name employee.employee_name%TYPE,
employee_surname employee.employee_surname%TYPE );
TYPE list_emptype IS
TABLE OF emp_rec;
list_emp list_emptype;
END pkg_emp_rec;
Next you can store and retrieve from this collection in whichever block/procedure you want.
SET SERVEROUTPUT ON
BEGIN
SELECT
employee_name,
employee_surname
BULK COLLECT INTO
pkg_emp_rec.list_emp
FROM
employee;
FOR indx IN 1..pkg_emp_rec.list_emp.count
LOOP
dbms_output.put_line(pkg_emp_rec.list_emp(indx).employee_name);
END LOOP;
END;
/
Note: This is not recommended when your table data is huge. It is always better to fetch from the database table than collections. Only take this as a solution for your homework.
Another approach is to store the employee names as a nested table column, which I give you as an assignment.

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!

How to check null element in a plsql nested table?

I have a PLSQL nested table of a record.
type rec is record
(a number,
b number);
type tab is table of rec;
v_tab tab;
I am populating this collection from a bulk query;
select x,y bulk collect into v_tab from testtable;
Now I need to find is there any record in v_tab collection where v_tab.a is null. Can I do it without looping in collection (by using some methods)?
This may be outside the scope of where you want to go, and I don't know your data, but...
What about changing your table type to INDEX BY and use NVL(A, -999) as the index?
Then you could test
IF v_tab.EXISTS(-999)
THEN
-- Handle null value
...
END IF;

Insert into another table after manipulations

I have two tables with same structures in two different databases.
Now, I want to copy the contents from one table (say table1 of db1) into another table (say table2 of db2), but I want to perform certain manipulations of some of the columns before inserting.
For example if table1 of db1 has 50 columns, I want to manipulate some (say 5) columns and insert the rest as is
Following is what I have done.
I am writing a pl/sql script declaring as many variables as the table columns
then a cursor to select from table
and then iterating in a loop to perform manipulations on individual columns and inserting into table2
DECLARE
var1 datatype;
var2 datatype;
..
CURSOR MY_CUR is
SELECT * FROM table1;
BEGIN
FOR CUR_ROW in MY_CUR
LOOP
var1 := CUR_ROW.column1
var2 := CUR_ROW.column2
..
logic to modify some variables e.g. var1 := LPAD(var1,5,'1');
..
insert into table2#db2
(column1, column2 ..)
values
(var1, var2 ..);
END LOOP;
END;
This is very slow, is there any other efficient way to accomplish this?(perhaps BULK COLLECT or FORALL?) but using BULK COLLECT or FORALL, how could I perform manipulations on individual columns?
I am using Oracle10g
try to do one more thing build the collection using for loop which you are doing then insert it with bulk insert statement. first declare collection with row type of table which is used for insert. eg.
type table2_typ is table of table2#db2%rowtype
v_table2_typ table2_typ;
fill collection v_table2_typ in you basic for loop and then insert this using bulk collect insert eg.
FORALL i IN 1..v_table2_typ.COUNT
INSERT INTO t1 VALUES v_table2_typ(i);
you need to use limit clause and commit mechanism if your data volume is more.

Oracle - select a specific column from a ref cursor

My situation:
I have a table named Table1. It has lots of columns, one of them is Column1. I don't know the other columns, they may even change sometimes.
There is a strongly typed ref cursor type which returns Table1%rowtype, named cur_Table1.
I have a stored procedure named SP1 which has an out parameter of type cur_Table1. I'm calling this SP1 stored procedure from another database that only sees this stored procedure, but not the table or the type itself.
How do I select only Column1 from the returned cursor?
I know I can fetch into a record or as many variables as the cursor has columns, but I only know of one column's existence so I can't declare the complete record or correct number of variables.
You can do this with DBMS_SQL, but it ain't pretty.
Table and sample data (COLUMN1 has the numbers 1 - 10):
create table table1(column1 number, column2 date, column3 varchar2(1000), column4 clob);
insert into table1
select level, sysdate, level, level from dual connect by level <= 10;
commit;
Package with a procedure that opens a ref cursor and selects everything:
create or replace package test_pkg is
type cur_Table1 is ref cursor return table1%rowtype;
procedure sp1(p_cursor in out cur_table1);
end;
/
create or replace package body test_pkg is
procedure sp1(p_cursor in out cur_table1) is
begin
open p_cursor for select column1, column2, column3, column4 from table1;
end;
end;
/
PL/SQL block that reads COLUMN1 data from the ref cursor:
--Basic steps are: call procedure, convert cursor, describe and find columns,
--then fetch rows and retrieve column values.
--
--Each possible data type for COLUMN1 needs to be added here.
--Currently only NUMBER is supported.
declare
v_cursor sys_refcursor;
v_cursor_number number;
v_columns number;
v_desc_tab dbms_sql.desc_tab;
v_position number;
v_typecode number;
v_number_value number;
begin
--Call procedure to open cursor
test_pkg.sp1(v_cursor);
--Convert cursor to DBMS_SQL cursor
v_cursor_number := dbms_sql.to_cursor_number(rc => v_cursor);
--Get information on the columns
dbms_sql.describe_columns(v_cursor_number, v_columns, v_desc_tab);
--Loop through all the columns, find COLUMN1 position and type
for i in 1 .. v_desc_tab.count loop
if v_desc_tab(i).col_name = 'COLUMN1' then
v_position := i;
v_typecode := v_desc_tab(i).col_type;
--Pick COLUMN1 to be selected.
if v_typecode = dbms_types.typecode_number then
dbms_sql.define_column(v_cursor_number, i, v_number_value);
--...repeat for every possible type.
end if;
end if;
end loop;
--Fetch all the rows, then get the relevant column value and print it
while dbms_sql.fetch_rows(v_cursor_number) > 0 loop
if v_typecode = dbms_types.typecode_number then
dbms_sql.column_value(v_cursor_number, v_position, v_number_value);
dbms_output.put_line('Value: '||v_number_value);
--...repeat for every possible type
end if;
end loop;
end;
/
Given the original question, jonearles's answer is still correct, so I'll leave it marked as such, but I ended up doing something completely different and much better.
The problem was/is that I have no control over SP1's database, I just have to call it from somewhere else as a 3rd party client. Now I managed to get permission to see not only SP, but also the type of the cursor. I still don't see the table but now there is a much cleaner solution:
In the other database I have been granted access to see this type now:
type cur_Table1 is ref cursor return Table1%rowtype;
So in my database I can do this now:
mycursor OtherDB.cur_Table1;
myrecord mycursor%rowtype;
...
OtherDB.SP1(mycursor);
fetch mycursor into myrecord;
dbms_output.put_line(myrecord.Column1);
See, I still don't need any access to the table, I see the cursor only. The key is that the magical %rowtype works for cursors as well, not just tables. It doesn't work on a sys_refcursor, but it does on a strongly typed one. Given this code, I don't have to care if anything changes on the other side, I don't have to define all the columns or records at all, I just specify the one column I'm interested in.
I really love this OOP attitude about Oracle.
Don't know if it's an option or not, but wouldn't a better solution be to create a function that returns the specific value you're looking for? That avoids the overhead of sending the extra data. Alternatively, you could define a cursor with a set of known fields in it that both parties know about.

Resources