In below code am trying to pass nestedTable payment_obj from parent procedure to child procedure, but in child procedure am not able to insert data into nested table as it is throwing even after initialization in parent procedure ERROR- Reference to uninitialized collection"
*Cause: An element or member function of a nested table or varray
was referenced (where an initialized collection is needed)
without the collection having been initialized.
I am collecting data into nested table which need to insert into payment table in child procedure and inserting data into payment table that is collected in nested table in parent procedure. How to achieve this type of requirement if it is not possible with nested table.
CREATE OR REPLACE
PACKAGE pkg_payment
AS
PROCEDURE PROCESS_PAYMENTS;
END pkg_payment;
CREATE OR REPLACE
PACKAGE body pkg_payment
AS
type typ_payment
IS
record
(
payment_id payment1.payment_id%type,
);
type tab_typ_payment
IS
TABLE OF typ_payment;
PROCEDURE prepare_payments(payment_obj OUT tab_typ_payment )
IS
tab_typ_payment_INDEX NUMBER ( 2 ) := 0 ;
BEGIN
payment_obj.extend;
tab_typ_payment_INDEX:= payment_obj.count;
payment_obj ( tab_typ_payment_INDEX ) .payment_id :=tab_typ_payment_INDEX;
END prepare_payments;
PROCEDURE PROCESS_PAYMENTS
IS
payment_obj tab_typ_payment:= tab_typ_payment();
CURSOR C_PUIDS_CUR
IS
(
select puid from payments
);
type PUIDS
IS
TABLE OF C_PUIDS_CUR%rowtype;
PUIDS_OBJ PUIDS := PUIDS();
BEGIN
OPEN C_PUIDS_CUR;
LOOP
FETCH C_PUIDS_CUR bulk collect INTO PUIDS_OBJ limit 100;
BEGIN
FOR I_ROW_PUID IN 1 .. PUIDS_OBJ .count
LOOP
prepare_payments( payment_obj);
END LOOP; -- FOR LOOP
END; -- END for fetch begin
EXIT
WHEN C_PUIDS_CUR %notfound;
END LOOP; -- End for limit loop
CLOSE C_PUIDS_CUR ;
forall i IN 1 .. payment_obj.count SAVE EXCEPTIONS
INSERT INTO payment1 VALUES payment_obj
(i
);
END PROCESS_PAYMENTS;
END pkg_payment;
Procedure prepare_payments needs to initialise payment_obj.
I'm not sure what this is all meant to do, but the following runs without error:
create table payment1 (payment_id number);
create table payments (puid integer);
insert into payments (puid) values (1);
create or replace package pkg_payment
as
procedure process_payments;
end pkg_payment;
/
create or replace package body pkg_payment as
type typ_payment is record
( payment_id payment1.payment_id%type );
type tab_typ_payment is table of typ_payment;
procedure prepare_payments
( payment_obj out tab_typ_payment )
is
tab_typ_payment_index number(2) := 0;
begin
payment_obj := tab_typ_payment(); -- Initialised here
payment_obj.extend;
tab_typ_payment_index := payment_obj.count;
payment_obj(tab_typ_payment_index).payment_id := tab_typ_payment_index;
end prepare_payments;
procedure process_payments is
payment_obj tab_typ_payment := tab_typ_payment();
cursor c_puids_cur is
select puid from payments;
type puids is table of c_puids_cur%rowtype;
puids_obj puids := puids();
begin
open c_puids_cur;
loop
fetch c_puids_cur bulk collect into puids_obj limit 100;
for i_row_puid in 1 .. puids_obj.count loop
prepare_payments(payment_obj);
end loop;
exit when c_puids_cur%notfound;
end loop;
close c_puids_cur;
forall i in 1 .. payment_obj.count save exceptions
insert into payment1 values payment_obj (i);
end process_payments;
end pkg_payment;
Related
My task is to insert entries from one table to another by using parametrized cursor. Here's what I have tried
DECLARE
oldroll NUMBER;
newroll NUMBER;
oldname VARCHAR2(25);
newname VARCHAR2(25);
CURSOR c_orollcall (roll_no NUMBER,name VARCHAR2) IS SELECT * FROM o_rollcall;
PROCEDURE procedure_2;
PROCEDURE procedure_2 AS
BEGIN
OPEN c_orollcall;
LOOP
FETCH c_orollcall INTO oldroll ,oldname;
SET #count = 0;
SELECT roll_no INTO #count FROM n_rollcall WHERE EXISTS (oldroll);
IF #count>0 THEN
DBMS_OUTPUT.PUT_LINE('ENTRY ALREADY EXISTS');
ELSE
INSERT INTO n_rollcall VALUES (oldroll,oldname);
END IF;
EXIT WHEN c_orollcall%NOTFOUND;
END LOOP;
CLOSE c_orollcall;
END;
/
BEGIN
procedure_2;
END
/
I am getting a bunch of errors and dont' understand how to proceed further.I previously posted a question about this too but it generated more errors .
here's the problem statement:
Write a PL/SQL block of code using parameterized Cursor that will merge the data available in
the newly created table N_RollCall with the data available in the table O_RollCall. If the data in
the first table already exist in the second table then that data should be skipped.
I would suggest you to use an extra variable to store the result and check it in IF codition
SET count = 0;
SELECT COUNT(roll_no) INTO count FROM n_rollcall WHERE EXISTS (oldroll);
Updated code:
CREATE OR REPLACE PROCEDURE procedure_2 AS
DECLARE count INT default 0;
BEGIN
OPEN c_orollcall;
LOOP
FETCH c_orollcall INTO oldroll ,oldname;
SET count = 0;
SELECT roll_no INTO count FROM n_rollcall WHERE EXISTS (oldroll);
IF count>0 THEN
DBMS_OUTPUT.PUT_LINE('ENTRY ALREADY EXISTS');
ELSE
INSERT INTO n_rollcall VALUES (oldroll,oldname);
END IF;
EXIT WHEN c_orollcall%NOTFOUND;
END LOOP;
CLOSE c_orollcall;
END;
/
create or replace procedure ins_act
as
l_result sys_refcursor;
l_id1 varchar2(32);
l_id2 varchar2(32);
l_id3 varchar2(32);
l_pid varchar2(16);
l_ac varchar2(32);
l_activity_date varchar2(32);
l_file_id varchar2(64):='FILE_' ||
to_char(sysdate,'MM/DD/YYYY');
begin
-- Procedure to pull the get records
get_act(l_result);
loop
-- Bulk fetch
fetch l_result bulk collect into
l_id1,l_id2,l_id3,l_platform_id,l_ac,l_activity_date,l_file_id
limit 100;
-- I need to insert into a temp table here.
-- Using bulk collect to increase the process speed and there are no
-- tables matching the l_result output type.
-- temp table
insert into temp(
id1,id2,id3,platform_id,activity_code,update_timestamp,file_id)
values(to_char(to_date(l_id1,'mm/dd/yyyy'),'mm/dd/yyyy'),
l_id2,l_id3,0,l_activity_code,sysdate,l_file_id);
exit when l_result%notfound;
end loop;
close l_result;*/
end;
/
I need to insert the sys_refcursor l_result records into the temp table. Please suggest the best method to achieve this. There will be around 250 k - 1 million records inserted
I understand you need a BULK operation to complete your requirement. See below how you can do it. Also read the comments inline.
create or replace procedure ins_act
as
l_result sys_refcursor;
--Created a RECORD type to hold the result of the SYS_REFCURSOR
TYPE RSLT IS RECORD
(
l_id1 varchar2(32),
l_id2 varchar2(32),
l_id3 varchar2(32),
l_pid varchar2(16),
l_ac varchar2(32),
l_activity_date varchar2(32),
l_file_id varchar2(64)
) ;
--Created associative array to hold the result
Type v_reslt is table of RSLT index by PLS_INETEGER;
--Variable to Record type
var_reslt v_reslt;
begin
-- Procedure to pull the get records
get_act(l_result);
-- Bulk fetch
Loop
fetch l_result bulk collect into var_reslt limit 100;
--Bulk Insert
FORALL I INTO 1..var_reslt.count SAVE EXCEPTIONS
insert into temp(id1,
id2,
id3,
platform_id,
activity_code,
update_timestamp,
file_id)
values
(to_char(to_date(var_reslt(i).l_id1,'mm/dd/yyyy'),'mm/dd/yyyy'),
var_reslt(i).l_id2,
var_reslt(i).l_id3,
0,
var_reslt(i).l_ac,
var_reslt(i).l_activity_date,
var_reslt(i).l_file_id);
exit when l_result%notfound;
end loop;
Commit;
close l_result;
Exception
WHEN OTHERS
THEN
--Bulk Exception handling
FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
DBMS_OUTPUT.put_line (
SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX
|| ‘: ‘
|| SQL%BULK_EXCEPTIONS (indx).ERROR_CODE);
RAISE;
end;
/
PS: Not tested.
In relation to my Previous Question, Include RowId value in Nested Table,
I have the below sample table:
create table data_test
(
data_id number,
data_value varchar2(100),
batch_name varchar2(100)
);
I have used this table as a parameter which includes the rowid:
create or replace package dat_pkg is
type typ_dat_rec is record (
data_rec data_test%rowtype,
data_rowid rowid);
type typ_dat_tst is table of typ_dat_rec index by pls_integer;
procedure transform_dat (p_batch_name data_test.batch_name%type);
procedure proc_test (p_dat typ_dat_tst);
end dat_pkg;
/
Using the procedure transform_dat, I want to populate the variable l_dat_rec with the filtered records from the data_test table, transform the data, and finally update the records using the proc_test procedure:
create or replace package body dat_pkg is
procedure transform_dat (p_batch_name data_test.batch_name%type)
is
cursor cur_dat is
select rowid, a.*
from data_test a
where batch_name = p_batch_name;
l_dat_rec typ_dat_tst;
begin
open cur_dat;
fetch cur_dat
BULK COLLECT
into l_dat_rec;
close cur_dat;
-- Do the Transformation here. Example --
for i in l_dat_rec.count loop
if l_dat_rec(i).data_value = 'hello' then
l_dat_rec(i).data_id := l_dat_rec(i).data_id + l_dat_rec(i).data_id;
else
l_dat_rec(i).data_id := l_dat_rec(i).data_id * l_dat_rec(i).data_id;
end if;
end loop;
-- update the table
proc_test (p_dat => l_dat_rec);
end transform_dat;
procedure proc_test (p_dat typ_dat_tst)
is
begin
for i in 1..p_dat.count loop
update data_test
set data_value = p_dat(i).data_value
where data_id = p_dat(i).data_id
and rowid = p_dat(i).data_rowid;
end loop;
end proc_test;
end dat_pkg;
/
however i am getting the error PLS-00597: expression 'L_DAT_REC' in the INTO list is of wrong type. The same error is being raised when i use BULK COLLECT.
What should I use to populate l_dat_rec?
In the answer to your previous question, I mentioned that populating the collection would be harder with the %rowtype field. As far as I'm aware, unless you declare an SQL-level object type instead of a record type you can't use bulk collect for this (though it's worth checking if that has changed in 12c perhaps).
I believe you are stuck with using a simpler cursor loop that builds the two fields in your type (i.e. the %rowtype sub-field and the rowid field) separately, and then builds up the collection a line at a time:
create or replace package body dat_pkg is
procedure transform_dat (p_batch_name data_test.batch_name%type)
is
cursor cur_dat is
select rowid, a.*
from data_test a
where batch_name = p_batch_name;
l_dat_tst typ_dat_tst;
l_rec data_test%rowtype;
begin
for rec_dat in cur_dat loop
l_rec.data_id := rec_dat.data_id;
l_rec.data_value := rec_dat.data_value;
l_rec.batch_name := rec_dat.batch_name;
-- or use a counter you increment for this...
l_dat_tst(l_dat_tst.count + 1).data_rec := l_rec;
l_dat_tst(l_dat_tst.count).data_rowid := rec_dat.rowid;
end loop;
-- Do the Transformation here. Example --
for i in 1..l_dat_tst.count loop
if l_dat_tst(i).data_rec.data_value = 'hello' then
l_dat_tst(i).data_rec.data_value := 'was hello';
else
l_dat_tst(i).data_rec.data_value := 'was not hello';
end if;
end loop;
-- update the table
proc_test (p_dat => l_dat_tst);
end transform_dat;
procedure proc_test (p_dat typ_dat_tst)
is
begin
for i in 1..p_dat.count loop
update data_test
set data_value = p_dat(i).data_rec.data_value
where data_id = p_dat(i).data_rec.data_id
and rowid = p_dat(i).data_rowid;
end loop;
end proc_test;
end dat_pkg;
/
As also discussed before, the references to the sub-field-record's fields have to be qualified properly, so I've inserted .data_rec in the references in both procedures. I've changed the dummy transformation to modify the value instead of the ID, as that means no updates were ever going to happen.
Demo with some dummy data:
insert into data_test values (1, 'hello', 'test');
insert into data_test values (2, 'hello', 'test');
insert into data_test values (3, 'hello', 'exclude');
insert into data_test values (4, 'goodbye', 'test');
exec dat_pkg.transform_dat('test');
select * from data_test;
DATA_ID DATA_VALUE BATCH_NAME
---------- -------------------- --------------------
1 was hello test
2 was hello test
3 hello exclude
4 was not hello test
I have little problem. I am trying to insert value into table. This is working. But I would like to control if value id_trainer is existing in another table. I want this -> execute insertClub(1, 5, 'someName'); -> and if id_trainer 5 not exists in table Trainer, procedure gives me message about this. (I tried to translate it into eng. lng., so you can find some gramm. mistakes)
create or replace procedure insertClub
(id_club in number, id_trainer in number, clubName in varchar2)
is
begin
declare counter number;
select count(*) into counter from trianer tr where tr.id_trainer = id_trainer;
if counter = 0 then
DBMS_OUTPUT.PUT_LINE('Trainer with this ID not exists');
end if;
insert into club values(id_club, id_trainer, clubName);
exception
when dup_val_on_index then
DBMS_OUTPUT.PUT_LINE('Dup ID');
end;
/
There is some error in the procedure. Please run below code to create procedure:
create or replace procedure insertClub
(id_club in number, id_trainer in number, clubName in varchar2)
is
counter number;
begin
select count(*) into counter from trianer tr where tr.id_trainer = id_trainer;
if counter = 0 then
DBMS_OUTPUT.PUT_LINE('Trainer with this ID not exists');
end if;
insert into club values(id_club, id_trainer, clubName);
exception
when dup_val_on_index then
DBMS_OUTPUT.PUT_LINE('Dup ID');
end;
/
I want to take value from one table and throw into another table. I have function where Im doing a bulk collect into a list. A List of beans.
FUNCTION get_things_info ( p_part_id IN NUMBER)
RETURN bean_list
IS
thing_list bean_list;
BEGIN
SELECT thing_bean (id, file_name, file_type, dbms_lob.getlength(thing), auditable)
BULK COLLECT INTO thing_list
FROM part_things
WHERE part_id = p_part_id;
RETURN thing_list;
END get_things_info_by_id;
I want to take that list, iterate over it and put in a deleted table with the same data types. I have a procedure that does an insert based off some java code:
PROCEDURE insert_thing(p_thing_bean IN OUT NOCOPY file_thing_bean, p_user_id IN NUMBER)
IS
BEGIN
INSERT INTO deleted_part_things
(id, part_id, file_name, file_type, thing, editable)
VALUES ( p_thing_bean.id,
p_thing_bean.parent_id,
p_thing_bean.file_name,
p_thing_bean.file_type,
p_thing_bean.attachment,
p_thing_bean.editable);
END insert_thing;
It does not have to use this procedure. I just need to know how to loop over the list I got back from the first function and insert into the deleted_part_thing table
You could use FORALL
This would then iterate through the collection supplied as a parameter inserting the records as required, I have given you a similar example you can amend to suit your needs:
PROCEDURE insert_from_list (
p_bean_list IN bean_list
)
IS
BEGIN
FORALL x IN INDICES OF p_bean_list
INSERT INTO deleted_hot_part_attachments
(id, hot_part_id, file_name, file_type, attachment, auditable)
VALUES (
p_bean_list(x).id,
p_bean_list(x).parent_id,
p_bean_list(x).file_name,
p_bean_list(x).file_type,
p_bean_list(x).attachment,
p_bean_list(x).auditable
);
--
COMMIT;
END insert_from_list;
Hope it helps...
EDIT: If you are using 10g or earlier, you'll need to pull the records into a collection that is the same structure as the table to insert into before then calling:
FORALL x IN INDICES OF <new_collection>
INSERT INTO deleted_hot_part_attachments
VALUES <new_collection>(x);
So if the structure of deleted_hot_part_attachments matches exactly the collection type bean_list then your FORALL would be:
FORALL x IN INDICES OF p_bean_list
INSERT INTO deleted_hot_part_attachments
VALUES p_bean_list(x);
If it does not then you'll need something like:
PROCEDURE insert_from_list (
p_bean_list IN bean_list
)
IS
-- Declare collection to hold table values
TYPE dhpa_tabtype IS TABLE OF deleted_hot_part_attachments%ROWTYPE
INDEX BY PLS_INTEGER;
dhpa_tab dhpa_tabtype;
BEGIN
-- Loop through the bean list collection populating the
-- new dhpa_tab collection with the required values
FOR i IN p_bean_list.FIRST .. p_bean_list.LAST
LOOP
dhpa_tab(i).id := p_bean_list(i).id;
dhpa_tab(i).parent_id := p_bean_list(i).parent_id;
dhpa_tab(i).file_name := p_bean_list(i).file_name;
dhpa_tab(i).file_type := p_bean_list(i).file_type;
dhpa_tab(i).attachment := p_bean_list(i).attachment;
dhpa_tab(i).auditable := p_bean_list(i).auditable;
END LOOP;
-- Populate the table using the new dhpa_tab collection values
FORALL x IN INDICES OF dhpa_tab
INSERT INTO deleted_hot_part_attachments
VALUES dhpa_tab(x);
--
COMMIT;
END insert_from_list;
A more old school approach to looping through collections would be something like:
declare
type t_tab is table of varchar2(10);
tab t_tab;
tab_idx pls_integer;
begin
-- populate tab (dense at first)
select 'val' || level
bulk collect into tab
from dual connect by level <= 20;
-- make tab sparse (just for fun)
tab.delete(3); -- remove 3rd element
tab.extend(5); -- make space for 5 more elements
tab(25) := 'val25';
-- LOOP through table contents
tab_idx := tab.first;
loop
exit when tab_idx is null;
dbms_output.put_line('Element ' || tab_idx || ' is ' || tab(tab_idx));
tab_idx := tab.next(tab_idx);
end loop;
end;
Nice thing is that you don't have the same restrictions with forall