Are there any impact of update statement on for loop statement in oracle? - oracle

I have nested for loop which iterates same table. In inner loop I update a column in same table. But in for loop condition I check that updated column and I need to check this column not in the beginning but dynamically, so my for loop iterations will maybe greatly decrease.
Am I doing this correct or is for statement will not see updated column?
declare
control number(1);
dup number(10);
res varchar2(5);--TRUE or FALSE
BEGIN
dup :=0;
control :=0;
FOR aRow IN (SELECT MI_PRINX, geoloc,durum, ROWID FROM ORAHAN where durum=0)
LOOP
FOR bRow IN (SELECT MI_PRINX, geoloc, ROWID FROM ORAHAN WHERE ROWID>aRow.ROWID AND durum=0)
LOOP
BEGIN
--dbms_output.put_line('aRow' || aRow.Mi_Prinx || ' bRow' || bRow.Mi_Prinx);
select SDO_GEOM.RELATE(aRow.geoloc,'anyinteract', bRow.Geoloc,0.02) into res from dual;
if (res='TRUE')
THEN
Insert INTO ORAHANCROSSES values (aRow.MI_PRINX,bRow.MI_PRINX);
UPDATE ORAHAN SET DURUM=1 where rowid=bRow.Rowid;
control :=1;
--dbms_output.put_line(' added');
END IF;
EXCEPTION
WHEN DUP_VAL_ON_INDEX
THEN
dup := dup+1;
--dbms_output.put_line('duplicate');
--continue;
END;
END LOOP;
IF(control =1)
THEN
UPDATE ORAHAN SET DURUM=1 WHERE rowid=aRow.Rowid;
END IF;
control :=0;
END LOOP;
dbms_output.put_line('duplicate: '||dup);
END ;
Note: I use oracle 11g and pl/sql developer
Sorry my english.

Yes, the FOR statement will not see the updated DURUM column because the FOR statement will see all data as they were when the query was started! This is called read consistency and Oracle accomplishes this by using the generated UNDO data. That means it'll have more and more work to do (==run slower) as your FOR loop advances and the base table is updated!
It also means that your implementation will eventually run into a ORA-01555: snapshot too old error when the UNDO tablespace is exhausted.
You'll be probably better off using a SQL MERGE statement which should also run much faster.
e.g.:
Merge Into ORAHANCROSSES C
Using (Select aROW.MI_PRINX aROW_MI_PRIX,
aROW.GEOLOC aROW_GEOLOC,
bROW.MI_PRINX bROW_MI_PRIX,
bROW.GEOLOC bROW_GEOLOC,
SDO_GEOM.RELATE(aRow.geoloc,'anyinteract', bRow.Geoloc,0.02) RES
From ORAHAN aROW,
ORAHAN bROW
Where aROW.ROWID < bROW.ROWID
) Q
On (C.MI_PRIX1 = Q.aROW_MI_PRIX
and C.MI_PRIX2 = Q.bROW_MI_PRIX)
When Matched Then
Delete Where Q.RES = 'FALSE'
When Not Matched Then
Insert Values (Q.aROW_MI_PRIX, Q.bROW_MI_PRIX)
Where Q.RES = 'TRUE'
;
I'm not sure what you're trying to accomplish by ROWID>aRow.ROWID though
To use a certain order (in this case MI_PRINX) use the following technique:
Merge Into ORAHANCROSSES C
Using (With D as (select T.*, ROWNUM RN from (select MI_PRINX, GEOLOC from ORAHAN order by MI_PRINX) T)
Select aROW.MI_PRINX aROW_MI_PRIX,
aROW.GEOLOC aROW_GEOLOC,
bROW.MI_PRINX bROW_MI_PRIX,
bROW.GEOLOC bROW_GEOLOC,
SDO_GEOM.RELATE(aRow.geoloc,'anyinteract', bRow.Geoloc,0.02) RES
From D aROW,
D bROW
Where aROW.RN < bROW.RN
) Q
On (C.MI_PRIX1 = Q.aROW_MI_PRIX
and C.MI_PRIX2 = Q.bROW_MI_PRIX)
When Matched Then
Delete Where Q.RES = 'FALSE'
When Not Matched Then
Insert Values (Q.aROW_MI_PRIX, Q.bROW_MI_PRIX)
Where Q.RES = 'TRUE'
;
In case the query is taking too long, you might select * from v$session_longops where seconds_remaining >0 to find out when it'll be finished.

Related

PL/SQL cannot delete multiple rows

I have written simple code using PL/SQL to delete multiple rows from a table, but below code only deletes one row every i trigger it.
DECLARE
i number(2);
BEGIN
FOR i IN 1..4 LOOP
DELETE FROM table_name WHERE rownum = i;
dbms_output.put_line('i is: '|| i);
END LOOP;
END;
Can someone please suggest what is wrong with code?
ROWNUM is the nth row read.
select * from table_name where rownum = 1;
gets you the first row.
select * from table_name where rownum <= 2;
gets you the first two rows.
select * from table_name where rownum = 2;
gets you no rows, because you cannot read a second row without having read a first one.
This said, you'd have to replace
DELETE FROM table_name WHERE rownum = i;
with
DELETE FROM table_name WHERE rownum = 1;
But why would you do this anyway? Why delete arbitrarily picked records? Why use PL/SQL at all, rather than a mere DELETE FROM table_name WHERE rownum <= 4;?
What you need to understand is how Oracle processes ROWNUM. When assigning ROWNUM to a row, Oracle starts at 1 and only only increments the value when a row is selected; that is, when all conditions in the WHERE clause are met. Since our condition requires that ROWNUM is greater than 2 or equal to nth value, no rows are selected and ROWNUM is never incremented beyond 1.
If you really do wanna achieve it using PLSQL anot using SQL query as my friend Throsten has stated then please find a work around below.
I Created a dummy table test_c which holds 1 column (ID with number as its type).
set serveroutput on ;
DECLARE
i number(2);
j number(2);
counter number(10):=0;
BEGIN
FOR i IN 5..11 LOOP
if counter = 0 then
j:=i;
end if;
DELETE FROM test_c WHERE ID = (select id from (select id,rownum as ro from test_c order by id) where ro =j);
dbms_output.put_line('i is: '|| i);
counter:=counter+1;
END LOOP;
END;
Please note that this is not the right way to do it, but will work for your requirement.

Optimise pl/sql execution time

I have a pl/sql procedure (Oracle 11g) that takes a long time to execute
(3h for 195.000 rows).
So the goal is to speed it up.
The code uses bulk collect and for all supposedly to speed up dmls, however there are some computations that are done on bulk collected data. These are done inside a classical for loop. And this for loop also accesses some other tables (performs selects) in order to perform computations needed. I think that part is one that slows everything down.
Consider the code below (it is modified and stripped down version of the real code - just to give you a gist of what is going on):
procedure long_runnig_task is
TYPE my_record is RECORD(key1 number, key2 number, key3 number ,
key4 number, p1 number ,p2 number, place_holder1 number,
place_holder2 number,place_holder3 number );
TYPE my_record_table IS TABLE OF my_record;
l_data my_record_table;
cursor c is
select key1, key2 , key3 ,key4 ,
(select param from paramtable where param_id=1) p1,
(select param from paramtable where param_id=2) p2,
0 place_holder1 ,0 place_holder2, 0 place_holder3 from mytable where myflag=4;
begin
open c;
loop
begin
fetch c bulk collect into l_data limit 1000;
savepoint mysp;
FOR indx IN 1 .. l_data.COUNT
loop
--computations per record
select max(amount) into myValue1 from table3 where
key1=l_data(indx).kay1 and
key2=l_data(indx).kay2 and key3=l_data(indx).kay3 and key4=l_data(indx).kay4;
select amount into myValue2 from table4 where key1=l_data(indx).kay1
and
key2=l_data(indx).kay2 and key3=l_data(indx).kay3 and key4=l_data(indx).kay4;
select amount into myValue3 from table5 where key1=l_data(indx).kay1
and
key2=l_data(indx).key2 and key3=l_data(indx).key3 and key4=l_data(indx).key4;
l_data(indx).place_holder1 := myValue1;
l_data(indx).place_holder2 := someFunction(myValue2,l_data(indx).p1);
l_data(indx).palce_holder3 := myValue3*l_data(indx).p2;
end loop;
forall indx IN 1 .. l_data.COUNT
update table6 set v= l_data(indx).place_holder1 where key1=l_data(indx).key1
and
key2=l_data(indx).key2 and key3=l_data(indx).key3 and key4=l_data(indx).key4;
forall indx IN 1 .. l_data.COUNT
insert into table7(col1,col2,col3) values (l_data(indx).place_holder3,sysdate,l_data(indx).place_holder2/10);
exception when others then
rollback to mysp;
raise;
end;
exit when c%notfound;
end loop;
exception when others then
rollback;
raise;
end;
Any suggestions on how to optimise the performance of the above are welcome.
Thanks.
This could be done in two sql statements:
BEGIN
MERGE INTO table6 tgt
USING (SELECT key1,
key2,
key3,
key4,
(SELECT MAX(amount)
FROM table3 t3
WHERE t3.key1 = mt.key1
AND t3.key2 = mt.key2
AND t3.key3 = mt.key3
AND t3.key4 = mt.key4) place_holder1
FROM mytable mt
WHERE myflag = 4) src
ON (tgt.key1 = src.key1
AND tgt.key2 = src.key2
AND tgt.key3 = src.key3
AND tgt.key4 = src.key4)
WHEN MATCHED THEN
UPDATE SET tgt.v = src.place_holder1;
INSERT INTO table7
(col1,
col2,
col3)
SELECT place_holder3 * mt.p2 val3,
SYSDATE dt,
somefunction(place_holder2, mt.p1) val2,
FROM (SELECT (SELECT param
FROM paramtable
WHERE param_id = 1) p1,
(SELECT param
FROM paramtable
WHERE param_id = 2) p2,
(SELECT amount
FROM table4
WHERE t4.key1 = mt.key1
AND t4.key2 = mt.key2
AND t4.key3 = mt.key3
AND t4.key4 = mt.key4) place_holder2,
(SELECT amount
FROM table5
WHERE t5.key1 = mt.key1
AND t5.key2 = mt.key2
AND t5.key3 = mt.key3
AND t5.key4 = mt.key4)/10 place_holder3
FROM mytable mt
WHERE myflag = 4);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
/
N.B. this does assume that for each key1, key2, key3 and key4 in mytable there is a corresponding row in table3, table4 and table5. By doing scalar subqueries, if there isn't a row present in those tables, null will be returned, rather than an error being generated (no_data_found) in your current code. You'll have to amend the above procedure to generate an error if such a condition occurs.
The way I did this was to take the cursor sql and then add in the subsequent sql statements as scalar subqueries in the cursor's sql.
Then I noticed that the insert and update were using different values (the update used the place_holder1 value, the insert the place_holder2 and place_holder3 values (amended appropriately). Then it was just a matter of converting those into a MERGE statement to do the update, and an insert statement.

Using Rownum in Cursor Bulk Collect Oracle

I'm trying to use the rownum to simulate a column autonumbered as I need to use it as an ID. Since it is an ID, I look at the final table if no record with MAX (ID).
The problem I have is when I want to do arithmetic operations within the cursor or when you invoke, or when you want to use a function. The ROWNUM (v_id) field is empty me when I want to print with DBMS_OUTPUT . Anyone have any idea how to solve it without using sequences ?
Here put the sample code.
declare
max_id number;
CURSOR INSRT(w_max number) IS
SELECT f_max_fact_sap(to_number(V_ID),w_max) AS V_ID,Seriei,serief
FROM (SELECT To_Char(ROWNUM) AS V_ID, A.*
FROM (SELECT DISTINCT a.matnr, a.seriei, a.serief,a.xblnr,a.fecha_sap, ((SERIEF-SERIEI)+1) AS rango
FROM SOPFUN.TT_ZMOVIMI_FACTURADAS a
WHERE 0 =(SELECT COUNT(1)
FROM PA_ZMOVIMI_FACTURADAS B
WHERE A.SERIEI = B.SERIEI
AND A.SERIEF = B.SERIEF
AND A.MATNR = B.MATNR
AND A.FECHA_SAP=B.FECHA_SAP)
AND A.FECHA_SAP IS NOT NULL) A);
TYPE T_INSRT IS TABLE OF INSRT%ROWTYPE INDEX BY PLS_INTEGER;
V_INSRT T_INSRT;
begin
SELECT Max(Nvl(ID,10000)) INTO MAX_ID-- To Proof because the table is empty
FROM PA_ZMOVIMI_FACTURADAS;
OPEN INSRT(MAX_ID);
LOOP
FETCH INSRT BULK COLLECT INTO V_INSRT LIMIT 1000;
FOR I IN 1 .. V_INSRT.Count loop
DBMS_OUTPUT.PUT_LINE('ID: ' ||V_INSRT(I).V_ID||' SI: '||V_INSRT(I).SERIEI||' SI: '||V_INSRT(I).SERIEF||' OPERACION: '||to_char(f_max_fact_sap(V_INSRT(I).V_ID,MAX_ID)));
end loop;
EXIT WHEN INSRT%NOTFOUND;
END LOOP;
end;

bind array in cursor using plsql

declare
cursor lc is
select *
from (select a.lin, a.pr,
b.name, sum(a.up) as u,
sum (a.d) as d
from li_dy_4 a,
p_list b
where a.pr=b.id
and b.parent_id != 0
and a.partitionid <= 308
and a.partitionid >= 302
and a.pr in (91,80)
GROUP BY a.pr, b.name, a.lin
order by d desc) ;
rec lc%ROWTYPE;
BEGIN
open lc;
loop
FETCH lc into rec;
dbms_output.put_line(rec.pr);
exit when lc%NOTFOUND;
end loop;
close lc;
END;
the above statement works fine for me. What I am not capable of finding anything hopeful is changing the value after the "in" statement which is a.pr in (91,80)
I have listed the values here manually, but I want to pass it to the cursor as an array of numbers for a.pr column. In short I want to do a.pr = idlist wher idlist is an array. Please anyone tell me if my idea is possible.
Just want to remind you, the IN clause supports 1000 items only. And that could be the primary reason ,there's nothing called BULK BINDING for SELECT Queries. We have FORALL INSERT/UPDATE, which is like BULK BINDING. Unfortunately select has none.
But still you can achieve your requirement in a different fashion.
You can try a global temporary table(GTT) which a temporary table with "scope of the data inserted" is only to that session.
You can FORALL INSERT all your data for IN clause into that table, and join the TABLE to your Query.
Else you can have a nested table (if oracle 10g) or a simple pl/sql type itself (if oracle 11g), with all your IN class items as records and join it to your Query.
Example: Using NESTED TABLE , effective for less number(<10000) of items
CREATE TYPE pr AS OBJECT
(pr NUMBER);
/
CREATE TYPE prList AS TABLE OF pr;
/
declare
myPrList prList := prList ();
cursor lc is
select *
from (select a.lin, a.pr,
b.name, sum(a.up) as u,
sum (a.d) as d
from li_dy_4 a,
p_list b,
TABLE(CAST(myPrList as prList)) my_list
where a.pr=b.id
and b.parent_id != 0
and a.partitionid <= 308
and a.partitionid >= 302
and a.pr = my_list.pr
GROUP BY a.pr, b.name, a.lin
order by d desc) ;
rec lc%ROWTYPE;
BEGIN
/*Populate the Nested Table, with whatever collection you have */
myPrList := prList ( pr(91),
pr(80));
/*
Sample code: for populating from your TABLE OF NUMBER type
FOR I IN 1..your_input_array.COUNT
LOOP
myPrList.EXTEND;
myPrList(I) := pr(your_input_array(I));
END LOOP;
*/
open lc;
loop
FETCH lc into rec;
exit when lc%NOTFOUND; -- Your Exit WHEN condition should be checked afte FETCH iyself!
dbms_output.put_line(rec.pr);
end loop;
close lc;
END;
/
I don't know the exact structure of your global table but you can use the collection in cursor like this
declare
cursor c1 is
select last_name ls from empc;
type x is table of employees.last_name%type;
x1 x := x();
cnt integer :=0;
begin
for z in c1 loop
cnt := cnt +1;
x1.extend;
x1(cnt) := z.ls;
if x1(cnt) is NULL then-----------
DBMS_OUTPUT.PUT_LINE('ASHISH');
end if;
dbms_output.put_line(cnt || ' '|| x1(cnt));
end loop;
end;

Oracle: update non-unique field

I need to update a non-unique field. I have a table tbl:
create table tbl (A number(5));
Values in tbl: 1, 2, 2, 2 .. 2.
I need to replace all 2 with new non-unique values
New values: 1, 100, 101, 102, 103 ..
I wrote:
DECLARE
sql_stmt VARCHAR2(500);
cursor curs is
select A from tbl group by A having count(*)>1;
l_row curs%ROWTYPE;
i number(5);
new_mail VARCHAR2(20);
BEGIN
i:=100;
open curs;
loop
fetch curs into l_row;
exit when curs%notfound;
SQL_STMT := 'update tbl set a='||i||' where a='||l_row.A;
i:=i+1;
EXECUTE IMMEDIATE sql_stmt;
end loop;
close curs;
END;
/
But I got:
A
----------
1
100
...
100
What can be wrong? Why doesn't the loop work?
what about
update tbl
set a = 100 + rownum
where a in (
select a
from tbl
group by a
having count(*) > 1 )
the subquery finds duplicated A fields and the update gives them the unique identifier starting from 100. (you got other problems here like , what if id 100, 101.... already exists ).
first rule of PLSQL says that what ever you can do with SQL always do with SQL. writing straight up for loop cause allot of context switches between the sql and pl/sql engine. even if oracle automatically converts this to a bulk statement (10g<) it will still be faster with pure SQL.
Your cursor gets one row per unique value of A:
select A from tbl group by A having count(*)>1;
You need to get all the distinct rows that match those values. One way is to do this:
select a, r from (
select a, rowid as r, count(*) over (partition by a) as c
from tbl
) where c > 1;
... and then use the rowid values to do the update. I'm not sure why you're using dynamic SQL as it is not at all necessary, and you can simplify (IMO) the loop:
declare
i number(5);
begin
i:=100;
for l_row in (
select a, r from (
select a, rowid as r, count(*) over (partition by a) as c
from tbl
) where c > 1) loop
update tbl set a=i where rowid = l_row.r;
i:=i+1;
end loop;
end;
/
I've kept this as PL/SQL to show what was wrong with what you were attempting, but #haki is quite correct, you should (and can) do this in plain SQL if at all possible. Even if you need it to be PL/SQL because you're doing other work in the loop (as the new_mail field might suggest) then you might be able to still do a single update within the procedure, rather than one update per iteration around the loop.

Resources