Oracle - BULK UPDATE with CURSOR LOOP - oracle

I want to perform an update on a huge table on a table like this (I now it's not best practise):
TARGET_TABLE (
TICKET_ID number,
product_id number,
NET number(15,2),
VAT number(15,2));
http://sqlfiddle.com/#!4/d39ed/3
Aim: UPDATE TARGET_TABLE set NET=VAT, VAT=NET
I came up with a BULK UPDATE, but I get an ORA-00913: "To many values" at line 43 which I can't explain. Also, I don't know how to update two rows at once in that variant.
Could anyone help out?
DECLARE
-- new data
CURSOR new_data_cur IS
select
a.rowid,
a.TICKET_ID,
a.product_id,
b.NET,
b.VAT
from TARGET_TABLE a
join TARGET_TABLE_COPY b
on ( a.TICKET_ID=b.TICKET_ID AND a.product_id =b.product_id ) ;
TYPE new_data_type IS TABLE OF new_data_cur%rowtype INDEX BY PLS_INTEGER;
new_data_tab new_data_type;
TYPE row_id_type IS TABLE OF ROWID INDEX BY PLS_INTEGER;
row_id_tab row_id_type;
TYPE rt_update_cols IS RECORD (
NET TARGET_TABLE.NET%TYPE
-- VAT TARGET_TABLE.VAT%TYPE
);
TYPE update_cols_type IS
TABLE OF rt_update_cols INDEX BY PLS_INTEGER;
update_cols_tab1 update_cols_type;
--update_cols_tab2 update_cols_type;
dml_errors EXCEPTION;
PRAGMA exception_init ( dml_errors,-24381 );
BEGIN
OPEN new_data_cur;
LOOP
FETCH new_data_cur BULK COLLECT INTO new_data_tab LIMIT 50000;
EXIT WHEN new_data_tab.count=0;
FOR i IN new_data_tab.first..new_data_tab.last LOOP
row_id_tab(i) := new_data_tab(i).rowid;
update_cols_tab1(i).NET := new_data_tab(i).VAT;
-- update_cols_tab2(i).VAT := new_data_tab(i).NET;
END LOOP;
FORALL i IN new_data_tab.first..new_data_tab.last SAVE EXCEPTIONS # ORA-00913: To many values
UPDATE TARGET_TABLE
-- SET row = update_cols_tab(i)
SET row = update_cols_tab1(i)
-- row = update_cols_tab2(i)
WHERE ROWID = row_id_tab(i);
COMMIT;
EXIT WHEN new_data_tab.count=0;
END LOOP;
COMMIT;
CLOSE new_data_cur;
EXCEPTION
WHEN dml_errors THEN
FOR i IN 1..SQL%bulk_exceptions.count LOOP
dbms_output.put_line('Some error occured');
END LOOP;
END;

I believe you don't need an extra cursor where you are swapping the values
FOR i IN new_data_tab.first..new_data_tab.last LOOP
row_id_tab(i) := new_data_tab(i).rowid;
update_cols_tab1(i).NET := new_data_tab(i).VAT;
-- update_cols_tab2(i).VAT := new_data_tab(i).NET;
END LOOP;
So you code will use these values in your bulk update
DECLARE
-- new data
CURSOR new_data_cur IS
select
a.rowid,
a.TICKET_ID,
a.product_id,
b.NET,
b.VAT
from TARGET_TABLE a
join TARGET_TABLE_COPY b
on ( a.TICKET_ID=b.TICKET_ID AND a.product_id =b.product_id ) ;
TYPE new_data_type IS TABLE OF new_data_cur%rowtype INDEX BY PLS_INTEGER;
new_data_tab new_data_type;
TYPE row_id_type IS TABLE OF ROWID INDEX BY PLS_INTEGER;
row_id_tab row_id_type;
TYPE rt_update_cols IS RECORD (
NET TARGET_TABLE.NET%TYPE
-- VAT TARGET_TABLE.VAT%TYPE
);
TYPE update_cols_type IS
TABLE OF rt_update_cols INDEX BY PLS_INTEGER;
update_cols_tab1 update_cols_type;
--update_cols_tab2 update_cols_type;
dml_errors EXCEPTION;
PRAGMA exception_init ( dml_errors,-24381 );
BEGIN
OPEN new_data_cur;
LOOP
FETCH new_data_cur BULK COLLECT INTO new_data_tab LIMIT 50000;
EXIT WHEN new_data_tab.count=0;
FORALL i IN new_data_tab.first..new_data_tab.last SAVE EXCEPTIONS # ORA-00913: To many values
UPDATE TARGET_TABLE
-- SET row = update_cols_tab(i)
-- SET row = update_cols_tab1(i)
-- row = update_cols_tab2(i)
NET = update_cols_tab1(i).VAT
VAT = update_cols_tab1(i).NET
WHERE ROWID = row_id_tab(i);
COMMIT;
EXIT WHEN new_data_tab.count=0;
END LOOP;
COMMIT;
CLOSE new_data_cur;
EXCEPTION
WHEN dml_errors THEN
FOR i IN 1..SQL%bulk_exceptions.count LOOP
dbms_output.put_line('Some error occured');
END LOOP;
END;

Related

Oracle Loop Rule 4809

I need to write a procedure that will insert thousands of rows in a table and use the auto generated id resulted from these rows and use it in other inserts.
I used a for loop in which I save the sequence id in a variable then use it in my inserts.
declare
first_id integer;
BEGIN
FOR texts in (select distinct text from table_texts )
LOOP
first_id := SEQ_IDS_OBJECTID.NEXTVAL;
INSERT INTO table_1(id,some_fields)
VALUES (first_id, 'blablabla');
insert into table_2 (id,text_field)
VALUES (first_id, texts.text);
END LOOP;
commit;
END;
I think that this is not the ideal way to achieve what I need. Also when I enter the code in TOAD , I get the following warning :
Rule 4809 (A loop that contains DML statements should be refactored to use BULK COLLECT and FORALL)
Is there better way to do it?
EDIT:
the above code was simplified. But I think I have to expose more of it to explain the case :
declare
first_id integer;
second_id integer;
BEGIN
FOR texts in (select distinct text1 , text2 from mdf )
LOOP
first_id := XAKTA.SEQ_IDS_OBJECTID.NEXTVAL;
select id_1 into second_id from table_3 where field_1 =texts.text1 ;
INSERT INTO table_1(id_1,id_2,some_fields)
VALUES (first_id ,second_id ,'blablabla');
insert into table_2 (id,text1,text2)
VALUES (first_id, texts.text1,texts.text2);
END LOOP;
commit;
END;
You can use FORALL to insert batches of items from your cursor:
DECLARE
TYPE texts_tab IS TABLE OF table_texts.text%TYPE;
TYPE ids_tab IS TABLE OF table_2.id%TYPE;
p_texts texts_tab;
p_ids ids_tab;
CURSOR c IS
SELECT DISTINCT text FROM table_texts;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO p_texts LIMIT 100;
FORALL i IN 1 .. p_texts.COUNT
INSERT INTO table_2 ( id, text_field )
VALUES ( SEQ_IDS_OBJECTID.NEXTVAL, p_texts(i) )
RETURNING id BULK COLLECT INTO p_ids;
FORALL i IN 1 .. p_ids.COUNT
INSERT INTO table_1( id, some_fields )
VALUES ( p_ids(i), 'blablabla' );
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
COMMIT;
END;
/
db<>fiddle here

Get Specific Row From Table Type With my Filter Values

I have procedure like as below:
cursor my_cursor is
select first_column, second_column, third_column from table_name;
TYPE my_cursor_type is TABLE OF my_cursor_type%ROWTYPE INDEX BY BINARY INTEGER;
my_cur my_cursor_type;
TYPE table_type IS TABLE OF table_name%ROW_TYPE INDEX BY BINARY INTEGER;
table_obj table_type;
begin
open my_cursor;
loop
fetch my_cursor bulk collect
into my_cur limit 5000;
exit when my_cursor%notfound;
for i in 1 .. my_cur.count loop
table_obj(i).first_column := my_cur(i).first_column;
table_obj(i).second_column := my_cur(i).second_column;
table_obj(i).third_column := my_cur(i).third_column;
end loop;
end loop;
Close my_cursor;
……
Now after these codes ı have table_obj which has 100000 record. And this table object has first_column, second_column, third_column.
I search one record's third_column in table_obj. I know first_column, second_column, and i search third_column in table_obj. I must fetch row from table_obj rows. This searched row has my first_column, second_column values.
And i get third_column_value from this row. How can i get specific row from table_obj rows with plsql ?
This can be done by transforming the first two columns to a single index. Concatenation with a separator works best for that. The separator should be a Character that is not used in either of the first two columns.
DECLARE
CURSOR my_cursor
IS
SELECT first_column, second_column, third_column FROM table_name;
TYPE my_cursor_type IS TABLE OF my_cursor_type%ROWTYPE
INDEX BY BINARY_INTEGER;
my_cur my_cursor_type;
TYPE table_type IS TABLE OF table_name%ROWTYPE
INDEX BY BINARY_INTEGER;
table_obj table_type;
l_composite_idx VARCHAR2( 61 );
l_test_idx_1 VARCHAR2( 30 ) := 'Test1';
l_test_idx_2 VARCHAR2( 30 ) := 'Success';
BEGIN
OPEN my_cursor;
LOOP
FETCH my_cursor BULK COLLECT INTO my_cur LIMIT 5000;
FOR i IN 1 .. my_cur.COUNT
LOOP
l_composite_idx := my_cur( i ).first_column || '&' || my_cur( i ).second_column;
table_obj( l_composite_idx ).first_column := my_cur( i ).first_column;
table_obj( l_composite_idx ).second_column := my_cur( i ).second_column;
table_obj( l_composite_idx ).third_column := my_cur( i ).third_column;
END LOOP;
EXIT WHEN my_cursor%NOTFOUND;
END LOOP;
CLOSE my_cursor;
-- You access a row in my_cur like this:
DBMS_OUTPUT.put_line( 'Result: ' || table_obj( l_test_idx_1 || '&' || l_test_idx_2 ) );
END;
Hope this helps!

How to remove ORA-01562: Failed to extend rollback segment error while updating 100 million rows

A table is having 100 million records and I need to update a column by adding 10% into the salary of each employee. when I execute update statement I am getting this error:
ORA-01562: Failed to extend rollback segment
How can I update this column for the best performance result?
update employee
set salary = salary + (salary*10/100)
OR
declare
i number(10);
limit number(10) := 100000;
begin
for i in 1 .. limit loop
update employee
set salary = salary + (salary*10/100)
where rownum = i;
limit := limit + 100000;
end loop;
end;
Looks like you are using Oracle version 8i or prior, as the rollback segments have been replaced with undo segments from Oracle 9i onwards.
To solve the problem, I would suggest you to check the trace file to see which rollback segment is creating the problem, then create a bigger rollback segment depending upon the update transaction size.
Try this:
DECLARE
CURSOR CUR
IS
SELECT ROWID, A.*
FROM YOUR_SALARY_TABLE A;
TYPE CUR_TYPE IS TABLE OF CUR%ROWTYPE
INDEX BY PLS_INTEGER;
L_CUR CUR_TYPE;
LIM NUMBER := 100000; -- Update chunk size
BEGIN
OPEN CUR;
LOOP
FETCH CUR BULK COLLECT INTO L_CUR LIMIT LIM;
FOR INDX IN 1 .. L_CUR.COUNT
LOOP
UPDATE YOUR_SALARY_TABLE S
SET S.SALARY_COLUMN = S.SALARY_COLUMN * 2 -- Multiplying here
WHERE ROWID = L_CUR (INDX).ROWID;
END LOOP;
COMMIT;
EXIT WHEN L_CUR.COUNT < LIM;
END LOOP;
CLOSE CUR;
END;
You can try this approach: link
Information about parallel: link
We can use FORALL also to achieve what is required. Hope this below snippet helps.
DROP TABLE test_so1
/
CREATE TABLE TEST_SO1
( COL1 NUMBER, COL2 VARCHAR2(100)
)
/
--Insert values
INSERT INTO TEST_SO1
SELECT LEVEL,'AVRAJIT'||LEVEL FROM DUAL CONNECT BY LEVEL < 10000
/
--FORALL UPDATE
DECLARE
type TEST_REC
IS
RECORD
(
COL1 NUMBER,
COL2 VARCHAR2(100),
col3 VARCHAR2(100));
type TEST_TAB
IS
TABLE OF TEST_REC;
LV_TAB TEST_TAB;
CURSOR LV_CUR
IS
SELECT col1,col2,rowid FROM TEST_SO1;
BEGIN
OPEN LV_CUR;
LOOP
FETCH LV_CUR BULK COLLECT INTO LV_TAB LIMIT 1000;
EXIT
WHEN LV_TAB.COUNT=0;
FORALL I IN LV_TAB.FIRST..LV_TAB.LAST
UPDATE TEST_SO1 SET COL2 = 'shubhojit' WHERE ROWID = lv_tab(i).col3;
COMMIT;
END LOOP;
END;
/

How to store a column of result of select query in an array?

If we have a column in a table of type number, how can we store the result of select query on that column in an array ?
This sample uses a list (table of numbers) to achieve this, because i find
those lists much more handy:
CREATE OR REPLACE TYPE numberlist AS TABLE OF NUMBER;
DECLARE
v_numberlist numberlist;
BEGIN
SELECT intval numbercolumn
BULK COLLECT INTO v_numberlist
FROM lookup;
FOR i IN 1..v_numberlist.count
LOOP
dbms_output.put_line( v_numberlist(i) );
END LOOP;
END;
Create a type which store number:-
CREATE OR REPLACE TYPE varray is table of number;
--write your select query inside for loop () where i am extracting through level
declare
p varray := varray();
BEGIN
for i in (select level from dual connect by level <= 10) loop
p.extend;
p(p.count) := i.level;
end loop;
for xarr in (select column_value from table(cast(p as varray))) loop
dbms_output.put_line(xarr.column_value);
end loop;
END;
output:-
1
2
3
4
5
6
7
8
9
10
Just an option to use some native SQL datatype. Hope it helps.
SET SERVEROUTPUT ON;
DECLARE
lv_num_tab DBMS_SQL.NUMBER_TABLE;
BEGIN
SELECT LEVEL BULK COLLECT INTO lv_num_tab FROM DUAL CONNECT BY LEVEL < 10;
FOR I IN lv_num_tab.FIRST..lv_num_tab.LAST
LOOP
dbms_output.put_line(lv_num_tab(i));
END LOOP;
END;
You may also want to put the whole select in a table. You can use a BULK COLLECT to an array:
CREATE OR REPLACE TYPE t_my_list AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE
PROCEDURE get_tables(p_owner in varchar2)
as
v_res t_my_list;
v_qry varchar2(4000) := '';
begin
v_qry := ' SELECT table_name from all_tables where owner='''||p_owner||'''';
dbms_output.put_line(v_qry);
-- all at once in the table
execute immediate v_qry bulk collect into v_res;
FOR I in 1..v_res.count
loop
dbms_output.put_line(v_res(i));
end loop;
exception
when others then
raise;
end get_tables;
/
begin
get_tables('E') ;
end;
/

how to get row which uses bulk insert and forall to update base table

I have a temp table(around 760k rows ) with no primary key. i am trying to insert rows from this temp table to my main table using bulk collect and forall(also used save exceptions to catch the rejected rows) , i have successfully done this. But i need to keep track of rows which are getting rejected and which are moved succesfully(what i want to do is update status column of temp table as 'E' for error and M for succeeful migration)
here is my procedure:
code snippet
desc temp_table:
col1 varchar2(30);
col2 varchar2(30);
col3 number;
col4 number;
status varchar2(1);
create or replace procedure mov_to_main_table
as
loop_count number default 0;
error_row_no number default 0;
sql_stmt varchar2(500);
cursor c_data is
select * from temp_table,a
where temp_table.col1=a.col;
TYPE t_bulk_collect_tab IS TABLE OF c_data%ROWTYPE;
l_tab t_bulk_collect_tab;
l_inserted t_bulk_collect_tab;
BEGIN
OPEN c_data;
LOOP
FETCH c_data
BULK COLLECT INTO l_tab LIMIT 1000;
EXIT WHEN l_tab.count = 0;
BEGIN
FORALL i IN 1..l_tab.count save exceptions
insert into main_table(col1,col2,col3)
values(l_tab(i).col1,l_tab(i).col2,l_tab(i),col3);
EXCEPTION
when others then
bulk_error_count := sql%bulk_exceptions.count;
--dbms_output.put_line('number of error rows :'||bulk_error_count );
for i in 1..bulk_error_count
loop
error_row_no := to_number(SQL%BULK_EXCEPTIONS(i).ERROR_INDEX)+100*loop_count;
sql_stmt := 'update temp_table set status=''E'' where rowid in (select rowid from temp_table where rownum <=:1 minus select rowid from temp_table where rownum<:2)';
execute immediate sql_stmt using error_row_no,error_row_no;
end loop;
end;
In above code i am updating the row which went into a exception part as 'E'
but for the row which is inserting succesfully iam unable to update the status ,
primarily i need to catch that rownum or rowid to update which iam unable to get from for all
how to update the succefully moved row?
please help me out
thanks in advance..
You approach is quite clever but there are a few things that won't work properly:
First, you don't need dynamic SQL here, drop that execute immediate.
You can still access the l_tab nested table in the exception block, so it's easy to pick the relevant id or rowid.
Last, this is not how paging queries are used in Oracle, see examples on SO.
My advice would be to fetch the rowid at the same time as the rest of the data:
CURSOR c_data IS
SELECT temp_table.*, a.*, temp_table.rowid rid
FROM temp_table, a
WHERE temp_table.col1 = a.col;
Then in the exception block you could find and update the offending row:
EXCEPTION
WHEN OTHERS THEN
bulk_error_count := SQL%bulk_exceptions.count;
--dbms_output.put_line('number of error rows :'||bulk_error_count );
FOR i IN 1 .. bulk_error_count LOOP
UPDATE temp_table
SET status = 'E'
WHERE rowid = l_tab(SQL%BULK_EXCEPTIONS(i).error_index).rid);
END LOOP;
END;

Resources