How do I use bulk collect and insert in Pl/SQl - sql

I want to fetch around 6 millions rows from one table and insert them all into another table.
How do I do it using BULK COLLECT and FORALL ?

declare
-- define array type of the new table
TYPE new_table_array_type IS TABLE OF NEW_TABLE%ROWTYPE INDEX BY BINARY_INTEGER;
-- define array object of new table
new_table_array_object new_table_array_type;
-- fetch size on bulk operation, scale the value to tweak
-- performance optimization over IO and memory usage
fetch_size NUMBER := 5000;
-- define select statment of old table
-- select desiered columns of OLD_TABLE to be filled in NEW_TABLE
CURSOR old_table_cursor IS
select * from OLD_TABLE;
BEGIN
OPEN old_table_cursor;
loop
-- bulk fetch(read) operation
FETCH old_table_cursor BULK COLLECT
INTO new_table_array_object LIMIT fetch_size;
EXIT WHEN old_table_cursor%NOTFOUND;
-- do your business logic here (if any)
-- FOR i IN 1 .. new_table_array_object.COUNT LOOP
-- new_table_array_object(i).some_column := 'HELLO PLSQL';
-- END LOOP;
-- bulk Insert operation
FORALL i IN INDICES OF new_table_array_object SAVE EXCEPTIONS
INSERT INTO NEW_TABLE VALUES new_table_array_object(i);
COMMIT;
END LOOP;
CLOSE old_table_cursor;
End;
Hope this helps.

oracle
Below is an example From
CREATE OR REPLACE PROCEDURE fast_way IS
TYPE PartNum IS TABLE OF parent.part_num%TYPE
INDEX BY BINARY_INTEGER;
pnum_t PartNum;
TYPE PartName IS TABLE OF parent.part_name%TYPE
INDEX BY BINARY_INTEGER;
pnam_t PartName;
BEGIN
SELECT part_num, part_name
BULK COLLECT INTO pnum_t, pnam_t
FROM parent;
FOR i IN pnum_t.FIRST .. pnum_t.LAST
LOOP
pnum_t(i) := pnum_t(i) * 10;
END LOOP;
FORALL i IN pnum_t.FIRST .. pnum_t.LAST
INSERT INTO child
(part_num, part_name)
VALUES
(pnum_t(i), pnam_t(i));
COMMIT;
END

The SQL engine parse and executes the SQL Statements but in some cases ,returns data to the PL/SQL engine.
During execution a PL/SQL statement, every SQL statement cause a context switch between the two engine. When the PL/SQL engine find the SQL statement, it stop and pass the control to SQL engine. The SQL engine execute the statement and returns back to the data in to PL/SQL engine. This transfer of control is call Context switch. Generally switching is very fast between PL/SQL engine but the context switch performed large no of time hurt performance .
SQL engine retrieves all the rows and load them into the collection and switch back to PL/SQL engine. Using bulk collect multiple row can be fetched with single context switch.
Example : 1
DECLARE
Type stcode_Tab IS TABLE OF demo_bulk_collect.storycode%TYPE;
Type category_Tab IS TABLE OF demo_bulk_collect.category%TYPE;
s_code stcode_Tab;
cat_tab category_Tab;
Start_Time NUMBER;
End_Time NUMBER;
CURSOR c1 IS
select storycode,category from DEMO_BULK_COLLECT;
BEGIN
Start_Time:= DBMS_UTILITY.GET_TIME;
FOR rec in c1
LOOP
NULL;
--insert into bulk_collect_a values(rec.storycode,rec.category);
END LOOP;
End_Time:= DBMS_UTILITY.GET_TIME;
DBMS_OUTPUT.PUT_LINE('Time for Standard Fetch :-' ||(End_Time-Start_Time) ||' Sec');
Start_Time:= DBMS_UTILITY.GET_TIME;
Open c1;
FETCH c1 BULK COLLECT INTO s_code,cat_tab;
Close c1;
FOR x in s_code.FIRST..s_code.LAST
LOOP
null;
END LOOP;
End_Time:= DBMS_UTILITY.GET_TIME;
DBMS_OUTPUT.PUT_LINE('Using Bulk collect fetch time :-' ||(End_Time-Start_Time) ||' Sec');
END;

CREATE OR REPLACE PROCEDURE APPS.XXPPL_xxhil_wrmtd_bulk
AS
CURSOR cur_postship_line
IS
SELECT ab.process, ab.machine, ab.batch_no, ab.sales_ord_no, ab.spec_no,
ab.fg_item_desc brand_job_name, ab.OPERATOR, ab.rundate, ab.shift,
ab.in_qty1 input_kg, ab.out_qty1 output_kg, ab.by_qty1 waste_kg,
-- null,
xxppl_reports_pkg.cf_waste_per (ab.org_id,ab.process,ab.out_qty1,ab.by_qty1,ab.batch_no,ab.shift,ab.rundate) waste_percentage,
ab.reason_desc reasons, ab.cause_desc cause,
to_char(to_date(ab.rundate),'MON')month,
to_char(to_date(ab.rundate),'yyyy')year,
xxppl_org_name (ab.org_id) plant
FROM (SELECT a.org_id,
(SELECT so_line_no
FROM xx_ppl_logbook_h x
WHERE x.batch_no = a.batch_no
AND x.org_id = a.org_id) so_line_no,
(SELECT spec_no
FROM xx_ppl_logbook_h x
WHERE x.batch_no = a.batch_no
AND x.org_id = a.org_id
and x.SALES_ORD_NO=a.sales_ord_no
and x.OPRN_ID =a.OPRN_ID
and x.RESOURCES = a.RESOURCES) SPEC_NO,
(SELECT OPERATOR
FROM xx_ppl_logbook_f y, xx_ppl_logbook_h z
WHERE y.org_id = a.org_id
AND y.batch_no = a.batch_no
AND y.rundate = a.rundate
AND a.process = y.process
AND a.resources = y.resources
AND a.oprn_id = y.oprn_id
AND z.org_id = y.org_id
AND z.batch_no = y.batch_no
AND z.batch_id = z.batch_id
AND z.rundate = y.rundate
AND z.process = y.process) OPERATOR,
a.batch_no, a.oprn_id, a.rundate, a.fg_item_desc,
a.resources, a.machine, a.sales_ord_no, a.process,
a.process_desc, a.tech_desc, a.sub_invt, a.by_qty1,
a.by_qty2, a.um1, a.um2, a.shift,
DECODE (shift, 'I', 1, 'II', 2, 'III', 3) shift_rank,
a.reason_desc, a.in_qty1, a.out_qty1, a.cause_desc
FROM xxppl_byproduct_waste_v a
WHERE 1 = 1
--AND a.org_id = (CASE WHEN :p_orgid IS NULL THEN a.org_id ELSE :p_orgid END)
AND a.rundate BETWEEN to_date('01-'||to_char(add_months(TRUNC(TO_DATE(sysdate,'DD-MON-RRRR')) +1, -2),'MON-RRRR'),'DD-MON-RRRR') AND SYSDATE
---AND a.process = (CASE WHEN :p_process IS NULL THEN a.process ELSE :p_process END)
) ab;
TYPE postship_list IS TABLE OF xxhil_wrmtd_tab%ROWTYPE;
postship_trns postship_list;
v_err_count NUMBER;
BEGIN
delete from xxhil_wrmtd_tab;
OPEN cur_postship_line;
FETCH cur_postship_line
BULK COLLECT INTO postship_trns;
FORALL i IN postship_trns.FIRST .. postship_trns.LAST SAVE EXCEPTIONS
INSERT INTO xxhil_wrmtd_tab
VALUES postship_trns (i);
CLOSE cur_postship_line;
COMMIT;
END;
/
---- INSERING DATA INTO TABLE WITH THE HELP OF CUROSR

Related

Using FORALL in Oracle with Update and insert

I'm new in PL/SQL.
I have a procedure like:
create or replace procedure insert_charge is
v_count number;
begin
for i in (select t.name, t.hire_date, t.salary
from emp t
where t.create_date >= (sysdate - 30)
and t.salary = 0) loop
insert into charge
(name, hire_date, salary)
values
(i.name, hire_date, salary);
commit;
update emp l
set l.status = 1
where l.name = i.name
and l.status = 0
and l.hire_date = i.hire_date;
commit;
end loop;
exception
when others then
rollback;
end insert_charge;
How can use FORALL statement instead of this?
You can't.
The FORALL statement runs one DML statement multiple times
ONE DML statement. You have two (update and insert).
As of code you wrote:
move COMMIT out of the loop
remove that when others "handler" as it handles nothing. If error happens, Oracle will silently rollback and report that procedure completed successfully, while it - actually - failed
There are a few additional tasks for FORALL; namely defining a collection to define the bulk area and a variable of that collection type to contain the actual data. As a safety value you should place a LIMIT on the number of fetched at once. Bulk Collect/ Forall is a trade off of speed vs memory. And at a certain point (depending on your configuration) has diminishing returns. Besides the memory you use for it is unavailable to other processes in the database. Plat well with your fellow queries. Then as #Littlefoot points out DO NOT SQUASH EXCEPTIONS log them and re-raise. Finally, a note about commits. **Do not commit after each DML statement, You may want spend some time to investigate [transactions][1]. With this in mind your procedure becomes something like:
create or replace procedure insert_charge is
cursor c_emp_cur is
select t.name, t.hire_date, t.salary
from emp t
where t.create_date >= (sysdate - 30)
and t.salary = 0;
type c_emp_array_t is table of c_emp%rowtype ; -- define collection for rows selected
k_emp_rows_max constant integer := 500; -- defines the maximum rows per fetch
l_emp_list c_emp_array_t; -- define variable of rows collection
begin
open c_emp_cur;
loop
fetch c_emp_cur -- fetch up to LIMIT rows from cursor
bulk collect
into l_emp_collect
limit k_emp_rows_max;
forall i in 1 .. l_emp_collect.count -- run insert for ALL rows in the collection
insert into charge(name, hire_date, salary)
values( l_emp_collect(i).name
, l_emp_collect(i).hire_date
, l_emp_collect(i).salary);
forall i in 1 .. l_emp_collect.count -- run update for ALL rows in the collection
update emp l
set l.status = 1
where l.name = l_emp_collect(i).name
and l.status = 0
and l.hire_date = l_emp_collect(i).hire_date;
exit when c_emp_cur%notfound; -- no more rows so exit
end loop;
close c_emp_cur;
commit; -- JUST 1 COMMIT;
exception
when others then
generate_exception_log ('insert_charge', sysdate, sql_errm ); --ASSUMED Anonomous Transaction procedure for exception log table.
raise;
end insert_charge;
DISCLAIMER: Not tested.
[1]: https://www.techopedia.com/definition/16455/transaction-databases

How to store multiple rows in a variable in pl/sql function?

I'm writing a pl/sql function. I need to select multiple rows from select statement:
SELECT pel.ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
if i use:
SELECT pel.ceid
INTO v_ceid
it only stores one value, but i need to store all values that this select returns. Given that this is a function i can't just use simple select because i get error, "INTO - is expected."
You can use a record type to do that. The below example should work for you
DECLARE
TYPE v_array_type IS VARRAY (10) OF NUMBER;
var v_array_type;
BEGIN
SELECT x
BULK COLLECT INTO
var
FROM (
SELECT 1 x
FROM dual
UNION
SELECT 2 x
FROM dual
UNION
SELECT 3 x
FROM dual
);
FOR I IN 1..3 LOOP
dbms_output.put_line(var(I));
END LOOP;
END;
So in your case, it would be something like
select pel.ceid
BULK COLLECT INTO <variable which you create>
from pa_exception_list
where trunc(pel.creation_Date) >= trunc(sysdate-7);
If you really need to store multiple rows, check BULK COLLECT INTO statement and examples. But maybe FOR cursor LOOP and row-by-row processing would be better decision.
You may store all in a rowtype parameter and show whichever column you want to show( assuming ceid is your primary key column, col1 & 2 are some other columns of your table ) :
SQL> set serveroutput on;
SQL> declare
l_exp pa_exception_list%rowtype;
begin
for c in ( select *
from pa_exception_list pel
where trunc(pel.creation_date) >= trunc(SYSDATE-7)
) -- to select multiple rows
loop
select *
into l_exp
from pa_exception_list
where ceid = c.ceid; -- to render only one row( ceid is primary key )
dbms_output.put_line(l_exp.ceid||' - '||l_exp.col1||' - '||l_exp.col2); -- to show the results
end loop;
end;
/
SET SERVEROUTPUT ON
BEGIN
FOR rec IN (
--an implicit cursor is created here
SELECT pel.ceid AS ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
)
LOOP
dbms_output.put_line(rec.ceid);
END LOOP;
END;
/
Notes from here:
In this case, the cursor FOR LOOP declares, opens, fetches from, and
closes an implicit cursor. However, the implicit cursor is internal;
therefore, you cannot reference it.
Note that Oracle Database automatically optimizes a cursor FOR LOOP to
work similarly to a BULK COLLECT query. Although your code looks as if
it fetched one row at a time, Oracle Database fetches multiple rows at
a time and allows you to process each row individually.

Deletion of many number of records using BULK COLLECT

I am trying to delete many thousands of records using BULK COLLECT concept. The table's record uniqueness is defined by 4 columns viz., NUMBER_ID, JOB_VALUE, JOB_TYPE, JOB_DATE. I need to delete records based on these four columns. I wrote the following code. However it errors out saying 1. "V_JOB_VAL' is inappropriate as the left hand side of an assignment statement" 2. cursor attribute may not be applied to non-cursor "L_JOB_RID". Could you please suggest where I am doing wrong?
** Edited Code **
ALTER TABLE JOB_SAMPLE NOLOGGING;
ALTER SESSION ENABLE PARALLEL DML;
DECLARE
TYPE V_JOB_RID IS TABLE OF JOB_SAMPLE.JOB_ROW_ID%TYPE;
TYPE V_JOB_DT IS TABLE OF JOB_SAMPLE.JOB_DATE%TYPE;
TYPE V_JOB_TY IS TABLE OF JOB_SAMPLE.JOB_TYPE%TYPE;
TYPE V_JOB_VAL IS TABLE OF JOB_SAMPLE.JOB_VALUE%TYPE;
L_JOB_RID V_JOB_RID := V_JOB_RID();
L_JOB_DT V_JOB_DT := V_JOB_DT();
L_JOB_TY V_JOB_TY := V_JOB_TY();
L_JOB_VAL V_JOB_VAL := V_JOB_VAL();
L_DELETE_BUFFER PLS_INTEGER := 50000;
CURSOR CDELETE IS
SELECT /*+ PARALLEL(10) */
T3.JOB_ROW_ID,
T3.JOB_DATE,
T3.JOB_TYPE,
T3.JOB_VALUE
FROM JOB_T1 T1,
JOB_T2 T2,
JOB_SAMPLE T3
WHERE T1.ROW_ID = T2.JOB_T1_ID
AND T3.JOB_ROW_ID = T1.JOB_ID
AND T2.T2_VAL = T3.JOB_VALUE
AND T1.NAME = T3.JOB_TYPE
AND TO_DATE(T2.T2_DT,'DD-MON-YY') = TO_DATE(T3.JOB_DATE,'DD-MON-YY');
BEGIN
OPEN CDELETE;
LOOP
FETCH CDELETE BULK COLLECT INTO L_JOB_RID, L_JOB_DT, L_JOB_TY, L_JOB_VAL LIMIT L_DELETE_BUFFER;
EXIT WHEN L_JOB_RID.COUNT < L_DELETE_BUFFER;
FORALL I IN 1..L_JOB_RID.COUNT
DELETE /*+ PARALLEL(10) */ JOB_SAMPLE
WHERE JOB_ROW_ID = L_JOB_RID(I)
AND JOB_DATE = L_JOB_DT(I)
AND JOB_TYPE = L_JOB_TY(I)
AND JOB_VALUE = L_JOB_VAL(I);
COMMIT;
END LOOP;
CLOSE CDELETE;
COMMIT;
END;
ALTER SESSION DISABLE PARALLEL DML;
Please let me know.
Thank you !
Start by changing
FETCH CDELETE BULK COLLECT INTO L_JOB_RID, L_JOB_DT, L_JOB_TY, V_JOB_VAL
into
FETCH CDELETE BULK COLLECT INTO L_JOB_RID, L_JOB_DT, L_JOB_TY, L_JOB_VAL

PLS-00436: implementation restriction: cannot reference fields of BULK In-BIND table of records

I get this error when I call the procedure 'archive_things' which in turns gives the error at INSERT INTO deleted_part_things
(id, part_id, file_name, file_type, thing, editable)
what does this mean?
PROCEDURE archive_things ( p_part_id IN NUMBER )
IS
thing_list bean_list;
BEGIN
thing_list := get_thingss_info(p_part_id);
insert_deleted_things(thing_list);
END archive_things;
FUNCTION get_things_info ( p_part_id IN NUMBER)
RETURN bean_list
IS
attachment_list bean_list;
BEGIN
SELECT file_thing_bean (id, hot_part_id, file_name, file_type, thing, editable)
BULK COLLECT INTO thing_list
FROM part_things
WHERE part_id =hot_part_id;
RETURN thing_list;
END get_things_info;
PROCEDURE insert_deleted_things( p_bean_list IN bean_list )
IS BEGIN
FORALL x IN INDICES OF p_bean_list
INSERT INTO deleted_part_things
(id, part_id, file_name, file_type, thing, auditable) <<<<< ERROR HERE!!!!!
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).thing, p_bean_list(x).editable
);
END insert_deleted_things;
Two points:
In your last question you mentioned that you're running Oracle 10g. As Ollie pointed out in his answer you can't use the method you're using until 11g.
Why are you creating two procedures and a function? This could easily be one procedure. As the initial procedure you're calling calls the other two you gain nothing by splitting it and make it much more complicated.
PLS-00436 is a restriction prior to 11G whereby you're not able to reference columns in a rowtype in a forall. There are a few ways round it:
One, as Ollie suggested is to have the same number of columns in your table as in your bulk collect. This isn't always possible.
Tom Kyte suggests set operations. The restriction on this is the size of the bulk collect you're doing. If it's bigger than the amount of undo you're going to have problems. Also if you want to do something else with the data then you have to do that separately.
The last option ( that I know of I'm sure there are more ) is to collect your records into individual types rather than a rowtype as per the following. The downside of this is that it may not be as quick as Tom's method and it's by no mean as clear as Ollie's.
I've just noted Sathya's method, which would also work but requires a lot of SQL to be executed.
PROCEDURE archive_things ( p_part_id IN NUMBER ) IS
CURSOR c_get_all ( Cpart_id char) is
SELECT file_attachment_bean (id, hot_part_id, file_name
, file_type, attachment, auditable)
FROM hot_part_attachments
WHERE hot_part_id = Cpart_id;
t_id bean_list.id%type;
t_hot_part_id bean_list.hot_part_id%type;
t_file_name bean_list.file_name%type;
t_file_type bean_list.file_type%type;
t_attachment bean_list.attachment%type;
t_auditable bean_list.auditable%type;
BEGIN
OPEN c_get_all(p_part_id);
FETCH c_get_all bulk collect into
t_id, t_hot_part_id, t_file_name, t_file_type, t_attachment, t_auditable;
LOOP
EXIT WHEN t_id.count = 0;
FORALL x IN t_id.first .. t_id.last
INSERT INTO deleted_hot_part_attachments (id, hot_part_id, file_name, file_type
, attachment, auditable)
VALUES ( t_id(x), t_hot_part_id(x), t_file_name(x), t_file_type(x)
, t_attachment(x), t_auditable(x) );
COMMIT; -- You may want to do this outside the procedure.
END LOOP;
CLOSE c_get_all;
END;
SET SERVEROUTPUT ON;
declare
cursor c1 IS select
c_code,c_name,c_language
from t_country;
TYPE c1_tab is table of t_country%rowtype;
c1_insert c1_tab;
l_count number:=0;
begin
open c1;
loop
fetch c1
bulk collect into c1_insert
limit 10000;
forall i in 1 .. c1_insert.count
insert into t_country values (c1_insert(i).cCode,c1_insert(i).cName,c1_insert(i).cLanguage);
commit;
exit when c1%notfounD;
end loop;
CLOSE C1;
end;

Oracle SQL: Compare all values from 2 columns and exchange them

I have a Oracle DB with a table called myC. In this table I have a few row, two of them called myCheight, myCwidth.
I need to read these values and compare them like in IF myCheight > myCwidth DO switch the values.
I tried to read values from one row but didnt get it to work. I use Oracles Oracle SQL Developer.
This is what i came up with so far:
set serveroutput on;
DECLARE
cursor h is select * from MyC;
type htype is table of h%rowtype index by number;
stage_tab htype;
master_tab htype;
BEGIN
open h;
loop
fetch h bulk collect into stage_tab limit 500;
for i in 1 .. stage_tab.count loop
master_tab(stage_tab(i).id) := stage_tabe(i);
end loop;
exit when h%notfound;
end loop;
close h;
end;
Can't you just do this?
UPDATE myC
SET myCheight = myCwidth,
myCwidth = myCheight
WHERE myCheight > myCwidth