PL SQL batch processing with insert from select - oracle

I have to move the data from table A to table B (they have almost the same fields).
What I have now is a cursor, that iterates over the records that has to be moved, insert one record in the destination table and updates the is_processed field in the source table.
Something like:
BEGIN
FOR i IN (SELECT *
FROM A
WHERE A.IS_PROCESSED = 'N')
LOOP
INSERT INTO B(...) VALUES(i....);
UPDATE A SET IS_PROCESSED = 'Y' WHERE A.ID = i.ID;
COMMIT;
END LOOP;
END;
The questions is, how to do the same using INSERT FROM SELECT(without the loop) and then update IS_PROCESSED of all the moved rows?

There is no BULK COLLECT INTO for INSERT .. SELECT
May be you can try this. I don't think it's better than your LOOP.
DECLARE
TYPE l_src_tp IS TABLE OF t_source%ROWTYPE;
l_src_rows l_src_tp;
BEGIN
SELECT *
BULK COLLECT INTO l_src_rows
FROM t_source;
FORALL c IN l_src_rows.first .. l_src_rows.last
INSERT INTO t_dest (td_id, td_value)
VALUES (l_src_rows(c).ts_id, l_src_rows(c).ts_value);
FORALL c IN l_src_rows.first .. l_src_rows.last
UPDATE t_source
SET ts_is_proccesed = 'Y'
WHERE ts_id = l_src_rows(c).ts_id;
END;

If you reverse the order and first make update and then insert you can use:
DECLARE
ids sys.odcinumberlist;
BEGIN
UPDATE a SET is_processed = 'Y' WHERE is_processed = 'N' RETURNING id BULK COLLECT INTO ids;
INSERT INTO b (id) SELECT column_value id FROM TABLE(ids);
COMMIT;
END;
In the SELECT you can join the ids table and get other data from other tables you want to insert into b.

Hello I should prefer pure SQL rather than PLSQL. I don't know why another update statement is requires for this simpler task. Let me know if this helps.
INSERT INTO <new table>
SELECT col1,col2,
.....,'Y'
FROM <old_table>
WHERE processed_in = 'N';

Related

Oracle put resultset into variable in FORALL

I have the following plsql block
declare
TYPE t_mds_ids IS TABLE OF mds.id%TYPE;
l_mds_ids t_mds_ids;
l_mds_parents t_mds_parents;
begin
SELECT id BULK COLLECT INTO l_mds_ids FROM mds;
FORALL indx IN l_mds_ids.FIRST .. l_mds_ids.LAST
select l_mds_ids(indx), ch.id_employee_parent
into l_mds_parents
FROM hierarchy_all ch
CONNECT BY ch.id_employee = prior ch.id_employee_parent
START WITH ch.id_employee = l_mds_ids(indx);
EXECUTE IMMEDIATE 'truncate table mds_hierarchy_all';
insert into mds_hierarchy_all
select * from l_mds_parents;
end;
t_mds_parents declared as
create or replace type r_mds_parents as object (
id_mds number(5,0),
id_employee number(5,0)
);
/
create or replace type t_mds_parents as table of r_mds_parents;
/
And I get an exception ORA-00947: not enough values
I really need to put the resultset of multiple rows into variable of TABLE TYPE on each iteration of FORALL loop. I can't use BULK COLLECT into l_mds_parents as it's restricted inside of FORALL.
Is there only solution to use temporary table instead of table variable?
I don't think you can do this with forall. You could use nested loops:
declare
TYPE t_mds_ids IS TABLE OF mds.id%TYPE;
l_mds_ids t_mds_ids;
l_mds_parents t_mds_parents;
begin
SELECT id BULK COLLECT INTO l_mds_ids FROM mds;
l_mds_parents := NEW t_mds_parents();
FOR indx IN l_mds_ids.FIRST .. l_mds_ids.LAST LOOP
FOR rec IN (
select l_mds_ids(indx) as id_employee, ch.id_employee_parent
FROM hierarchy_all ch
CONNECT BY ch.id_employee = prior ch.id_employee_parent
START WITH ch.id_employee = l_mds_ids(indx)
) LOOP
l_mds_parents.extend();
l_mds_parents(l_mds_parents.COUNT)
:= NEW r_mds_parents (rec.id_employee, rec.id_employee_parent);
END LOOP;
END LOOP;
EXECUTE IMMEDIATE 'truncate table mds_hierarchy_all';
insert into mds_hierarchy_all
select * from table(l_mds_parents);
end;
/
But you don't need to use PL/SQL at all; use a single hierarchical query, or probably more simply here, recursive subquery factoring:
insert into mds_hierarchy_all /* (id_mds, id_employee) -- better to list columns */
with rcte (id_mds, id_employee) as (
select m.id, ha.id_employee_parent
from mds m
join hierarchy_all ha on ha.id_employee = m.id
union all
select r.id_mds, ha.id_employee_parent
from rcte r
join hierarchy_all ha on ha.id_employee = r.id_employee
)
select * from rcte;
db<>fiddle with some made-up data.

Is it possible to return the Primary Key on an Insert as select statement - Oracle?

So I usually get the Primary Key of a newly inserted record as the following while using a trigger.
insert into table1 (pk1, notes) values (null, "Tester") returning pk1
into v_item;
I am trying to use the same concept but with an insert using a select statement. So for example:
insert into table1 (pk1, notes) select null, description from table2 where pk2 = 2 returning pk1
into v_item;
Note:
1. There is a trigger on table1 which automatically creates a pk1 on insert.
2. I need to use a select insert because of the size of the table that is being inserted into.
3. The insert is basically a copy of the record, so there is only 1 record being inserted at a time.
Let me know if I can provide more information.
I don't believe you can do this with insert/select directly. However, you can do it with PL/SQL and FORALL. Given the constraint about the table size, you'll have to balance memory usage with performance using l_limit. Here's an example...
Given this table with 100 rows:
create table t (
c number generated by default as identity,
c2 number
);
insert into t (c2)
select rownum
from dual
connect by rownum <= 100;
You can do this:
declare
cursor t_cur
is
select c2
from t;
type t_ntt is table of number;
l_c2_vals_in t_ntt;
l_c_vals_out t_ntt;
l_limit number := 10;
begin
open t_cur;
loop
fetch t_cur bulk collect into l_c2_vals_in limit l_limit;
forall i in indices of l_c2_vals_in
insert into t (c2) values (l_c2_vals_in(i))
returning c bulk collect into l_c_vals_out;
-- You have access to the new ids here
dbms_output.put_line(l_c_vals_out.count);
exit when l_c2_vals_in.count < l_limit;
end loop;
close t_cur;
end;
You can't use that mechanism; as shown in the documentation railroad diagram:
the returning clause is only allowed with the values version, not with the subquery version.
I'm interpreting your second restriction (about 'table size') as being about the number of columns you would have to handle, possibly as individual variables, rather than about the number of rows - I don't see how that would be relevant here. There are ways to avoid having lots of per-column local variables though; you could select into a row-type variable first:
declare
v_item number;
v_row table1%rowtype;
begin
...
select null, description
into v_row
from table2 where pk2 = 2;
insert into table1 values v_row returning pk1 into v_item;
dbms_output.put_line(v_item);
...
or with a loop, which might make things look more complicated than necessary if you really only ever have a single row:
declare
v_item number;
begin
...
for r in (
select description
from table2 where pk2 = 2
)
loop
insert into table1 (notes) values (r.description) returning pk1 into v_item;
dbms_output.put_line(v_item);
...
end loop;
...
or with a collection... as #Dan has posted while I was answering this so I won't repeat! - though again that might be overkill or overly complicated for a single row.

Oracle iteration and collections

How I can iterate over loop and collect information inside variable/collection?
Something like:
cursor cursor_c= select col1 from table1 where condition;
collection l;
foreach row in cursor_c
l.add (select col2 from table2 where col1=row);
end;
printout(l);
I want to run this as script not inside a procedure.
I have 0 experience with PL/SQL so any help will be appreciated!
You can duplicate the logic shown, but except in unusual circumstances I wouldn't recommend doing it that way. Collections are available and useful in PL/SQL, but printing them out is done by looping over the collection - so if all you're doing is collecting something in-memory to print it out, the better choice would be simply to print the items coming from the cursor when the cursor is iterated. In addition, doing a singleton SELECT inside a loop, where the data being selected in the inner SELECT is dependent on the outer SELECT, is equivalent to doing a JOIN - so do the join instead of pinging the database with a bunch of single-row SELECTs. Putting this together I suggest doing something like:
BEGIN
FOR aRow in (SELECT t2.COL2
FROM TABLE1 t1
INNER JOIN TABLE2 t2
ON t2.COL1 = t1.COL1
WHERE t1.WHATEVER = vSOMETHING_ELSE)
LOOP
DBMS_OUTPUT.PUT_LINE(aRow.COL2);
END LOOP;
END;
In PL/SQL the best choice is generally to use a cursor to get the data in the form you want it, rather than collecting data and then iterating over the collection to transform it. Your data resides in the database - learn to work with it there.
Best of luck.
If you simply want a column in a table matching a column from another table, you won't need a loop, simply use JOINS
SELECT t2.col2
FROM table1 t1
JOIN table2 t2 ON t2.col1 = t1.col1
WHERE '<your_where_conditon>'
If you do want to display them using dbms_output, you may simply use an implicit cursor loop ( #Bob Jarvis answer) or BULK COLLECT to load into a collection.
DECLARE
TYPE col2type is TABLE OF table2.col2%TYPE;
col2_t col2type;
BEGIN
SELECT t2.col2 BULK COLLECT INTO col2_t
FROM table1 t1
INNER JOIN table2 t2 ON t2.col1 = t1.col1
WHERE '<your_where_conditon>';
for i in 1..col2_t.count
loop
dbms_output.put_line(col2_t(i).col2);
end loop;
END;
/
Below code snippet will solve your problem.
set serveroutput on;
declare
type ty_tb_name is table of varchar2(20) index by pls_integer;
l_tb_name ty_tb_name;
cursor cur_acc_name is
select account_name from cust_account;
idx number := 1;
begin
for rec in cur_acc_name loop
l_tb_name(idx) := rec.account_name;
idx := idx+1;
exit when cur_acc_name%notfound;
end loop;
DBMS_OUTPUT.put_line('count:'||l_tb_name.count);
for i in l_tb_name.first..l_tb_name.count loop
DBMS_OUTPUT.put_line('name:'||l_tb_name(i));
end loop;
exception
when others then
DBMS_OUTPUT.put_line(SQLERRM);
end;
/

While INSERT got error PLS-00904: stud.col3 is invalid identifier

In my stored procedure I want if the value of col1 & col2 match with employee then insert the unique record of the employee. If not found then match the value of col1, col2 & col3 with employee match then insert the value. If also not found while match all these column then insert the record by using another column.
Also one more thing that I want find list of values like emp_id by passing the another column value and if a single record can not match then make emp_id as NULL.
Also I want to insert one record at a time after match with txt along with others table having data like emp.
create or replace procedure sp_ex
as
cursor c1 is select * from txt%rowtype;
v_col1 tbl1.col1%type;
type record is table of txt%rowtype; --Staging table
v_rc record := record();
begin
open c1;
loop
fetch c1 bulk collect into v_rc limit 1000;
loop
for i in 1..v_rc.count loop
select col1 into v_col1 from tbl1
where exists (select col1 from tbl1 where tbl1.col1 = emp.col1);
insert
when txt.col1 = emp.col1 and txt.col2 = stud.col2 then
into main_table(columns) values(v_rc(i).col1, ...)
when txt.col1 = emp.col1 and txt.col2 = stud.col2 and txt.col3 = stud.col3 then
into main_table(columns) values(v_rc(i).col1, ...)
else
insert into main_table(columns) values(v_rc(i).col1, ...)
select * from txt;
end loop;
exit when v_rc.count < limit;
end loop;
close c1;
end sp_ex;
While emp, stud are the different tables where i have to match with txt.
In that Stored Proc I want to load data from txt into main_table in batch processing mode. The data would be match one by one record then after if matching condition match then load into the main table. How can i create the stored proc so that the Data will load by above logic one by one in batch processing. Could you please help me to share your idea. Thanks
The syntax seems to be rather mixed up.
Multi-table insert is like this:
insert all -- alternatively, "insert first"
when dummy = 'X' then
into demo (id) values (1)
when dummy = 'Y' then
into demo (id) values (2)
else
into demo (id) values (3)
select * from dual;
Or perhaps you wanted a PL/SQL case statement:
case
when dummy = 'X' then
insert into demo (id) values (1);
when dummy = 'Y' then
insert into demo (id) values (2);
else
insert into demo (id) values (3);
end case;
Instead there seems to be a mixture of the two.
Also there is a missing end loop, and an implicit cursor (select col1 from tbl1) with no into clause.

PLS 00357 Error- Table, View or Sequence "txt.col1" not allowed in the context

I have created one Stored Procedure. In that Stored Proc I want if the value of col1 & col2 match with employee then insert the unique record of the employee. If not found then match the value of col1, col2 & col3 with employee match then insert the value. If also not found while match all these column then insert the record by using another column.
Also one more thing that i want find list of values like emp_id by passing the another column value and if a single record can not match then make emp_id as NULL.
create or replace procedure sp_ex
AS
empID_in varchar2(10);
fname_in varchar2(20);
lname_in varchar2(30);
---------
type record is ref cursor return txt%rowtype; --Staging table
v_rc record;
rc rc%rowtype;
begin
open v_rc for select * from txt;
loop
fetch v_rc into rc;
exit when v_rc%notfound;
loop
select col1 from tbl1
Where EXISTS (select col1 from tbl1 where tbl1.col1 = rc.col1);
IF txt.col1 = rc.col1 AND txt.col2 = rc.col2 THEN
insert into main_table select distinct * from txt where txt.col2 = rc.col2;
ELSIF txt.col1 = rc.col1 AND txt.col2 = rc.col2 AND txt.col3 = rc.col3 THEN
insert into main_table select distinct * from txt where txt.col2 = rc.col2;
ELSE
insert into main_table select * from txt where txt.col4 = rc.col4;
end if;
end loop;
close v_rc;
end sp_ex;
I found an error while compile this Store Procedure PLS-00357: Table,View Or Sequence reference not allowed in this context. How to resolve this issue and how to insert value from staging to main table while using CASE or IF ELSIF statement. Could you please help me so that i can compile the Stored Proc.
Since I don't have your database to work with it's difficult to be 100% certain, but to my eye the line which reads
rc rc%rowtype;
should say
rc txt%rowtype;
You've defined the cursor v_rc as returning txt%rowtype, and your SQL statement used with this cursor is select * from txt, but that data type is at odds with the definition of rc. Thus, it appears you need to change rc as shown.
It also looks like the LOOP statement which comes immediately after exit when v_rc%notfound; should be removed, as there's nothing after that which would terminate that loop.
In addition, you have many references to columns in the txt table, e.g. IF txt.col1 = rc.col1. You can't refer to values in a table in this manner. I'm not quite sure what you're trying to do here so I can't really suggest anything.
Also, the statement
select col1 from tbl1
Where EXISTS (select col1 from tbl1 where tbl1.col1 = rc.col1);
is selecting a column from the database, but isn't putting it anywhere. This should be either a singleton SELECT (SELECT..INTO) or a cursor.
One more thing: you can't use distinct *. You need to use a column list with distinct.
Perhaps the following would be close to what you're trying to do:
create or replace procedure sp_ex
AS
begin
FOR rc IN (SELECT * FROM TXT)
LOOP
FOR t1 IN (SELECT *
FROM TBL1
WHERE TBL1.COL1 = rc.COL1)
LOOP
IF t1.COL1 = rc.COL1 AND
t1.COL2 = rc.COL2
THEN
insert into main_table
select *
from txt
where txt.col2 = rc.col2;
ELSIF t1.col1 = rc.col1 AND
t1.col2 = rc.col2 AND
t1.col3 = rc.col3
THEN
insert into main_table
select *
from txt
where txt.col2 = rc.col2;
ELSE
insert into main_table
select *
from txt
where txt.col4 = rc.col4;
END IF;
END LOOP; -- t1
END LOOP; -- rc
end sp_ex;
Best of luck.

Resources