performance tuning: for each vs forall - oracle

I have the following pl/sql procedure:
create or replace procedure processData (a_date Date, r_offset Number, r_limit Number) as
begin
for r in (select * from (select a.*, ROWNUM rnum from (select* from TABLE1 T1 where T1.date=a_date) a
where rownum <= r_limit) where rnum >= r_offset) loop
if (/*some condition on column values */) then
/* insert into A*/
else
/*insert into B*/
end if;
end loop;
end;
as you can see it is made with for each.
I was now wondering about doing that with for-all statement: this would involve gathering data inside of table variables and then, after populating those variables, perform 2 forall statements: one for table A and one for table B.
Would this greatly improve my program's performances?
edit:
I just noticed that this program is, in general, very very slow! I tried processing 10k records and it almost took 30secs! Where could the general problem be?

Why just don't use plain SQL - conditional insert:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9014.htm
I bet that it will be at least 100 times faster than any PL/SQL loop, even FORALL.
INSERT FIRST
WHEN /*some condition on column values */
THEN INTO /* insert into A */
ELSE INTO /*insert into B*/
select * from (select a.*, ROWNUM rnum from (select* from TABLE1 T1 where T1.date=a_date) a
where rownum <= r_limit) where rnum >= r_offset
Simple demo: http://sqlfiddle.com/#!4/d2019/1

Related

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

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.

Are there any impact of update statement on for loop statement in 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.

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.

SELECT COUNT(*) vs. fetching twice with an explicit cursor

I have read a book whose title is "Oracle PL SQL Programming" (2nd ed.) by Steven Feuerstein & Bill Pribyl. On page 99, there is a point suggested that
Do not "SELECT COUNT(*)" from a table unless you really need to know the total number of "hits." If you only need to know whether there is more than one match, simply fetch twice with an explicit cursor.
Could you anyone explain this point more to me by providing example? Thank you.
Update:
As Steven Feuerstein & Bill Pribyl recommends us not to use SELECT COUNT() to check whether records in a table exist or not, could anyone help me edit the code below in order to avoid using SELECT COUNT(*) by using explicit cursor instead? This code is written in the Oracle stored procedure.
I have a table emp(emp_id, emp_name, ...), so to check the provided employee ID corret or not:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
v_rows INTEGER;
BEGIN
...
SELECT COUNT(*) INTO v_rows
FROM emp
WHERE emp_id = emp_id_in;
IF v_rows > 0 THEN
/* do sth */
END;
/* more statements */
...
END do_sth;
There are a number of reasons why developers might perform select COUNT(*) from a table in a PL/SQL program:
1) They genuinely need to know how many rows there are in the table.
In this case there is no choice: select COUNT(*) and wait for the result. This will be pretty fast on many tables, but could take a while on a big table.
2) They just need to know whether a row exists or not.
This doesn't warrant counting all the rows in the table. A number of techniques are possible:
a) Explicit cursor method:
DECLARE
CURSOR c IS SELECT '1' dummy FROM mytable WHERE ...;
v VARCHAR2(1);
BEGIN
OPEN c;
FETCH c INTO v;
IF c%FOUND THEN
-- A row exists
...
ELSE
-- No row exists
...
END IF;
END;
b) SELECT INTO method
DECLARE
v VARCHAR2(1);
BEGIN
SELECT '1' INTO v FROM mytable
WHERE ...
AND ROWNUM=1; -- Stop fetching if 1 found
-- At least one row exists
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- No row exists
END;
c) SELECT COUNT(*) with ROWNUM method
DECLARE
cnt INTEGER;
BEGIN
SELECT COUNT(*) INTO cnt FROM mytable
WHERE ...
AND ROWNUM=1; -- Stop counting if 1 found
IF cnt = 0 THEN
-- No row found
ELSE
-- Row found
END IF;
END;
3) They need to know whether more than 1 row exists.
Variations on the techniques for (2) work:
a) Explicit cursor method:
DECLARE
CURSOR c IS SELECT '1' dummy FROM mytable WHERE ...;
v VARCHAR2(1);
BEGIN
OPEN c;
FETCH c INTO v;
FETCH c INTO v;
IF c%FOUND THEN
-- 2 or more rows exists
...
ELSE
-- 1 or 0 rows exist
...
END IF;
END;
b) SELECT INTO method
DECLARE
v VARCHAR2(1);
BEGIN
SELECT '1' INTO v FROM mytable
WHERE ... ;
-- Exactly 1 row exists
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- No row exists
WHEN TOO_MANY_ROWS THEN
-- More than 1 row exists
END;
c) SELECT COUNT(*) with ROWNUM method
DECLARE
cnt INTEGER;
BEGIN
SELECT COUNT(*) INTO cnt FROM mytable
WHERE ...
AND ROWNUM <= 2; -- Stop counting if 2 found
IF cnt = 0 THEN
-- No row found
IF cnt = 1 THEN
-- 1 row found
ELSE
-- More than 1 row found
END IF;
END;
Which method you use is largely a matter of preference (and some religious zealotry!) Steven Feuerstein has always favoured explicit cursors over implicit (SELECT INTO and cursor FOR loops); Tom Kyte favours implicit cursors (and I agree with him).
The important point is that to select COUNT(*) without restricting the ROWCOUNT is expensive and should therefore only be done when a count is trully needed.
As for your supplementary question about how to re-write this with an explicit cursor:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
v_rows INTEGER;
BEGIN
...
SELECT COUNT(*) INTO v_rows
FROM emp
WHERE emp_id = emp_id_in;
IF v_rows > 0 THEN
/* do sth */
END;
/* more statements */
...
END do_sth;
That would be:
CREATE OR REPLACE PROCEDURE do_sth ( emp_id_in IN emp.emp_id%TYPE )
IS
CURSOR c IS SELECT 1
FROM emp
WHERE emp_id = emp_id_in;
v_dummy INTEGER;
BEGIN
...
OPEN c;
FETCH c INTO v_dummy;
IF c%FOUND > 0 THEN
/* do sth */
END;
CLOSE c;
/* more statements */
...
END do_sth;
But really, in your example it is no better or worse, since you are selecting the primary key and Oracle is clever enough to know that it only needs to fetch once.
If two is all you are interested in, try
SELECT 'THERE ARE AT LEAST TWO ROWS IN THE TABLE'
FROM DUAL
WHERE 2 =
(
SELECT COUNT(*)
FROM TABLE
WHERE ROWNUM < 3
)
It will take less code than doing the manual cursor method,
and it is likely to be faster.
The rownum trick means to stop fetching rows once it has two of them.
If you don't put some sort of limit on the count(*), it could take a long while to finish, depending on the number of rows you have. In that case, using a cursor loop, to read 2 rows from the table manually, would be faster.
This comes from programmers writing code similar to the following (this is psuedo code!).
You want to check to see if the customer has more than one order:
if ((select count(*) from orders where customerid = :customerid) > 1)
{
....
}
That is a terribly inefficient way to do things. As Mark Brady would say, if you want to know if a jar contains pennies, would you count all the pennies in the jar, or just make sure there is 1 (or 2 in your example)?
This could be better written as:
if ((select 1 from (select 1 from orders where customerid = :customerid) where rownum = 2) == 1)
{
....
}
This prevents the "counting all of the coins" dilemma since Oracle will fetch 2 rows, then finish. The previous example would cause oracle to scan (an index or table) for ALL rows, then finish.
He means open a cursor and fetch not only the first record but the second, and then you will know there is more than one.
Since I never seem to need to know that SELECT COUNT(*) is >= 2, I have no idea why this is a useful idiom in any SQL variant. Either no records or at least one, sure, but not two or more. And anyway, there's always EXISTS.
That, and the fact that Oracle's optimizer seems to be pretty poor... - I would question the relevance of the technique.
To address TheSoftwareJedi's comments:
WITH CustomersWith2OrMoreOrders AS (
SELECT CustomerID
FROM Orders
GROUP BY CustomerID
HAVING COUNT(*) >= 2
)
SELECT Customer.*
FROM Customer
INNER JOIN CustomersWith2OrMoreOrders
ON Customer.CustomerID = CustomersWith2OrMoreOrders.CustomerID
Appropriately indexed, I've never had performance problems even with whole universe queries like this in SQL Server. However, I have consistently run into comments about Oracle optimizer problems here and on other sites.
My own experience with Oracle has not been good.
The comment from the OP appears to be saying that full COUNT(*) from tables are not well handled by the optimizer. i.e.:
IF EXISTS (SELECT COUNT(*) FROM table_name HAVING COUNT(*) >= 2)
BEGIN
END
(which, when a primary key exists, can be reduced to a simple index scan - in a case of extreme optimization, one can simply query the index metadata in sysindexes.rowcnt - to find the number of entries - all without a cursor) is to be generally avoided in favor of:
DECLARE CURSOR c IS SELECT something FROM table_name;
BEGIN
OPEN c
FETCH c INTO etc. x 2 and count rows and handle exceptions
END;
IF rc >= 2 THEN BEGIN
END
That, to me would result in less readable, less portable, and less maintainable code.
Before you take Steven Feuerstein's suggestions too serious, just do a little benchmark. Is count(*) noticeably slower than the explicit cursor in your case? No? Then better use the construct that allows for simple, readable code. Which, in most cases, would be "select count(*) into v_cnt ... if v_cnt>0 then ..."
PL/SQL allows for very readable programs. Don't waste that just to nano-optimize.
Depending on the DB, there may be a sys table which stores an approximate count and can be queried in constant time. Useful if you want to know whether the table has 20 rows or 20,000 or 20,000,000.
SQL Server:
if 2 = (
select count(*) from (
select top 2 * from (
select T = 1 union
select T = 2 union
select T = 3 ) t) t)
print 'At least two'
Also, don't ever use cursors. If you think you really really need them, beat yourself with a shovel until you change your mind. Let relics from an ancient past remain relics from an ancient past.
If you want to get number of rows in a table, please don't used count(*), I would suggest count(0) that 0 is the column index of your primary key column.

Resources