Populate an Associative Array inside a Nested Table - sql

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

Related

How to include a SUBSELECT in VALUES of INSERT to take values from different row?

I want to make a trigger that will insert a value from a connected row. For example I have a table with 3 rows as below:
I create a trigger that will work once row 3 and 4 are deleted (in this case will be deleted at the same time). And I want to record invnr and extinvnr from row 1 based on idparent=id. I cannot seem to make it work though.
CREATE OR REPLACE TRIGGER LOG_DELETEDPAYMENTS
BEFORE DELETE ON payments
FOR EACH ROW
BEGIN
IF :old.invnr IS NULL THEN
INSERT INTO TABLE_LOG_DELETEDPAYMENTS (table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
values ('payments', :old.invnr, :old.extinvnr, :old.invdate, :old:transactionid, :old.info, :old.createdby, sys_context('userenv','OS_USER'), SYSDATE);
END IF;
END;
How can I incorporate this into the trigger above?
Try it this way:
create or replace TRIGGER LOG_DELETEDPAYMENTS
BEFORE DELETE ON payments
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
Declare
my_invnr PAYMENTS.INVNR%TYPE;
my_extinvnr PAYMENTS.EXTINVNR%TYPE;
Begin
IF :old.INVNR IS NULL THEN
Select INVNR, EXTINVNR
Into my_invnr, my_extinvnr
From PAYMENTS
Where ID = :old.IDPARENT;
--
INSERT INTO TABLE_LOG_DELETEDPAYMENTS (table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
values ('payments', my_invnr, my_extinvnr, :old.invdate, :old:transactionid, :old.info, :old.createdby, sys_context('userenv','OS_USER'), SYSDATE);
END IF;
End;
END;
You should select the values of INVNR and EXTINVNR based on ID - IDPARENT relationship and store it in the variables (my_invnr and my_extinvnr).
Those variables are used in INSERT into the log statement.
Because of the Select ... Into statement that is reading the affected table - trigger would fail with table PAYMENTS is mutating error.
To avoid that (to separate transaction from the table) you should Declare the PRAGMA AUTONOMOUS_TRANSACTION.
There will be two rows inserted into LOG as the trigger runs FOR EACH (deleted) ROW.
Regards...
This assumes your are on release 12c or greater of Oracle database.
CREATE OR REPLACE PACKAGE LOG_DELETEDPAYMENTS_PKG
AS
-- need a locally defined type for use in trigger
TYPE t_payments_tbl IS TABLE OF payments%ROWTYPE INDEX BY PLS_INTEGER;
END LOG_DELETEDPAYMENTS_PKG;
CREATE OR REPLACE PACKAGE BODY LOG_DELETEDPAYMENTS_PKG
AS
BEGIN
-- could also put the trigger code here and pass the type as a parameter to a procedure
NULL;
END LOG_DELETEDPAYMENTS_PKG;
CREATE OR REPLACE TRIGGER LOG_DELETEDPAYMENTS_CT
FOR DELETE ON payments
COMPOUND TRIGGER
l_tab LOG_DELETEDPAYMENTS_PKG.t_payments_tbl;
l_count PLS_INTEGER:= 0;
BEFORE EACH ROW IS
BEGIN
-- capture the deletes in local type
l_count := l_count + 1;
l_tab(l_count).invnr := :old.invnr;
l_tab(l_count).extinvnr := :old.extinvnr;
l_tab(l_count).invdate := :old.invdate;
l_tab(l_count).transactionid := :old.transactionid;
l_tab(l_count).info := :old.info;
l_tab(l_count).createdby := :old.createdby;
l_tab(l_count).idparent := :old.idparent;
l_tab(l_count).id := :old.id;
END BEFORE EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN l_tab.first .. l_tab.COUNT LOOP
IF(l_tab(i).invnr IS NULL) THEN
-- if the invoice number is NULL, then get info from parent
SELECT p.invnr
,p.extinvnr
INTO l_tab(i).invnr
,l_tab(i).extinvnr
FROM TABLE(l_tab) p
WHERE p.id = l_tab(i).idparent;
END IF;
END LOOP;
-- log all deletes
FORALL i IN 1 .. l_tab.COUNT
INSERT INTO LOG_DELETEDPAYMENTS
(table_name, invnr, extinvnr, invdate, transactionid, info, createdby, deleted_by, date_of_delete)
VALUES
('payments', l_tab(i).invnr, l_tab(i).extinvnr, l_tab(i).invdate, l_tab(i).transactionid, l_tab(i).info, l_tab(i).createdby, sys_context('userenv','OS_USER'), SYSDATE);
l_tab.delete;
END AFTER STATEMENT;
END LOG_DELETEDPAYMENTS_CT;

How to Pass nested table into sub procedures

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;

sql insert procedure: insert values into table and control if value is existing in another table

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;
/

How to return multiple rows from oracle stored procedure from multiple cursors?

I need to have stored procedure where I can run multiple cursors.
Loop over each cursor and then do some operation on each row.
This way I will have the desired result from these cursors. Result of such multiple cursors then needs to be union with some other rows and then filtered out and return those rows finally from the proc.
Please note that each cusror and another queries will have same columns.
I am not sure how to do this in the oracle.
Please help me out.
create or replace PROCEDURE test_proc
(
-- some inputs
hc_cursor OUT SYS_REFCURSOR
)
IS
cursor cursor_one is
SELECT * FROM table_one ;
BEGIN
FOR current_row in cursor_one
loop
-- do some modification on each row and return each modified row
end loop;
cursor cursor_two is
SELECT * FROM table_one ;
BEGIN
FOR current_row in cursor_two
loop
-- do some modification on each row and return each modified row
-- append to result from first cursor
end loop;
-- union results from both these cusrors with some another query
-- now filter these records on some criterais
-- return finally
END;
My suggestion is going to be insert the rows from your cursor into a temporary table. Then join the temporary table with your existing table for the filter criteria you mention. Psuedocode:
create or replace function my_func
return sysrefcursor
is
cursor cursor_one is
SELECT * FROM table_one ;
cursor cursor_two is
SELECT * FROM table_one ;
BEGIN
FOR current_row in cursor_one
loop
-- do some modification on each row and insert into temporary table
end loop;
FOR current_row in cursor_two
loop
-- do some modification on each row and insert into temporary table
end loop;
-- results from cursor 1 and 2 exist in temporary table
open out_cursor for
select t.* from
my_temp_table t
join
my_other_table tt
on (t.col1 = tt.col1) -- or whatever columns are appropriate
where t.col2 = 'some criteria' -- or whatever filter criteria you like.
return out_cursor;
END;
create type emp_obj AS object
(
empno NUMBER (4)
,ename VARCHAR2(10)
,sal number(7,2)
,job varchar2(9)
);
CREATE TYPE EMP_NT AS TABLE OF emp_OBJ;
create or replace package test_pkg
IS
TYPE abc_cur is REF CURSOR;
procedure test_proc
(
p_rec IN OUT abc_cur
);
END test_pkg;
/
create or replace package body test_pkg
IS
procedure test_proc
(
p_rec IN OUT abc_cur
)
IS
v_emp_nt emp_nt;
BEGIN
SELECT emp_obj(empno,ename,sal,job) BULK COLLECT INTO v_emp_nt FROM EMP;
FOR i in v_emp_nt.first..v_emp_nt.last
LOOP
IF v_emp_nt(i).job='CLERK' THEN
v_emp_nt(i).sal := v_emp_nt(i).sal +200;
ELSIF v_emp_nt(i).job='MANAGER' THEN
v_emp_nt(i).sal := v_emp_nt(i).sal +800;
END IF;
END LOOP;
open p_rec for select * from table(v_emp_nt);
END test_proc;
END test_pkg;
/
As you have seen the code,what i do ,is to get the desired result in nested table(what your cursor is doing) ,and do some manipulation based on the resultant records ,as well as update the nested table.
At the end i will create a cursor from this updated nested table and return the cursor after opening.
Now your question :How can you return append cursor ?
It is simple create two nested table ,do some manipulation on both the nested table
Suppose you have v_emp_nt1 as first nested table ,you do some manipulation on that .
you have another v_emp_nt2 as second nested table ,you do some manipulation on that .
Now your cursor will be like
open p_rec FOR (select * from v_emp_nt1 union select * from v_empnt2);
With this way you can achieve your desired output .
**Note:**The above code is for one nested table ,you need to create another nested table for your code to get complete
create
package my_pkg as
type my_rec is record
(
<list your fields here>
);
type my_rec_tab is table of my_rec;
function get_my_rows
return my_rec_tab pipelined;
end my_pkg;
create
package body my_pkg as
function get_my_rows
return my_rec_tab pipelined
as
begin
for c_cur in (select * from table_one)
loop
-- do some modification on the current row and return the modified row
pipe row (c_cur);
end loop;
for c_cur in (select * from table_one)
loop
-- do some modification on the current row and return the modified row
pipe row (c_cur);
end loop;
return;
end get_my_rows;
end my_pkg;
select * from table(my_pkg.get_my_rows);

Loop over list and insert into table

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