Need help to create purging operation executing in loop - oracle

I am going to run one pl/sql block which deletes all the data which is older than 30 days in a table.
Condition is like this:
It will delete 200k data at a time and after deleting 200k data I have to give wait period for 10-15 sec. There are around 100 million records in that table. The whole deletion process I want to do it in automation script.
DECLARE
CURSOR c6
IS
SELECT /*+parallel(a,32)*/
a.rowid,a.*
FROM EB_O.CCO_DIR_CONTRS_ES_GG a
WHERE a.GG_CREATE_DATE < SYSDATE-30
AND ROWNUM <=200001;
TYPE contact_point_id_tab IS TABLE OF c6%ROWTYPE
INDEX BY PLS_INTEGER;
l_contact_point_id_tab contact_point_id_tab;
BEGIN
OPEN c6;
LOOP
FETCH c6
BULK COLLECT INTO l_contact_point_id_tab
LIMIT 10000;
EXIT WHEN l_contact_point_id_tab.COUNT = 0;
IF l_contact_point_id_tab.COUNT > 0
THEN
FORALL i
IN l_contact_point_id_tab.FIRST .. l_contact_point_id_tab.LAST
DELETE FROM EB_O.CCO_DIR_CONTRS_ES_GG
WHERE rowid =l_contact_point_id_tab (i).rowid;
COMMIT;
END IF;
l_contact_point_id_tab.delete;
END LOOP;
END;
This is the above plsql block I have written. How to do this operation in multiple loop and after every loop there will be wait period of 10-15 sec and again the deletion operation will happen for next 200k data. the loop will continue until all the data will be deleted.
N.B.: for wait period, grant is no there for DBMS_LOCK.sleep

I would not do this by looping over a cursor. Instead, I would do something like:
begin
loop
delete FROM EB_O.CCO_DIR_CONTRS_ES_GG
WHERE GG_CREATE_DATE < SYSDATE-30
AND ROWNUM <=200000;
exit when sql%rowcount = 0;
commit;
dbms_lock.sleep(10);
end loop;
end;
/
This way, you avoid the potential snapshot-too-old error (because you have committed across a cursor fetch loop), and you're no longer fetching information into memory to pass back to the database, which makes things more efficient.

If you don't have access to sleep function, just create your own.
Store the output of below query in a variable, create a loop which exits when sysdate is equal to the value stored in your variable.
select sysdate+(10 / (24*60*60)) from dual;
Also, instead of hardcoding 10 you can take the seconds as input variable and pass the same in the query.

Related

Oracle - two loops in procedure

I need some help in writing Oracle PL/SQL procedure that should do the following:
the procedure is called from a trigger after an update of the field in one table with the input parameter B-block or D-activate (this is already done)
the procedure should first open one cursor that will catch the account numbers of a client and open a loop that will process account by account
this one account should be forwarded to another loop that will catch card numbers of that client for that account (second cursor) and when into this loop, the card number should be used as an input parameter for a stored procedure that is called to block/unblock this card - this stored procedure already exists I just need to call it
the procedure don't need to return any parameters, the idea is just to block/activate card number of a client with the already written stored procedure for that
Should I write a package for this or just a procedure? And how can I write one loop in another?
I just realized that i can do this without cursors in a procedure. For simple example:
create or replace procedure blokiraj_proc (core_cust_id varchar2, kyc_blocked varchar2) as
type NumberArray is Array(100) of test_racuni.foracid%type;
type StringArray is Array (1000) of test_kartice.card_num%type;
accnt NumberArray;
card_number StringArray;
begin
select foracid bulk collect into accnt from test_racuni where cif_id = core_cust_id;
for i in accnt.first..accnt.last
loop
select card_num bulk collect into card_number from test_kartice where rbs_acct_num = accnt(i);
dbms_output.enable (100000);
dbms_output.put_line (accnt(i));
for j in 1..card_number.count
loop
dbms_output.put_line (card_number(j));
blokiraj_karticu (card_number(j));
end loop;
end loop;
end;
Is this a better approach then the curssors? And why is dbms_output not printing anything when i trigger the procedure?
As #EdStevens indicated you cannot avoid processing cursors. But you can avoid the looping structure of cursor within cursor. And the implicit open and close cursor for the inner one. The queries have combine into a simple JOIN then bulk collect into a single collection.
For this I created a RECORD to contain both the account number and card number; then a collection of that record. The cursor is then bulk collected into the collection. Your initial code allows for up to 100000 cards to be processed, and while I am a fan of bulk collect (when needed) I am not a fan of filling memory, therefore I limit the number of rows bulk collect gathers of each fetch. This unfortunately introduces a loop-within-loop construct, but the penalty is not near as great as cursor-within-cursor construct. The following is the result.
create or replace procedure blokiraj_proc (core_cust_id varchar2) as
type acct_card_r
is record(
acct_num test_kartice.rbs_acct_num%type
, card_num test_kartice.card_num%type
);
type acct_card_array is table of acct_card_r;
acct_card_list acct_card_array;
k_acct_card_buffer_limit constant integer := 997;
cursor c_acct_card(c_cust_id varchar2) is
select r.foracid
, k.card_num
from test_racuni r
left join test_kartice k
on (k.rbs_acct_num = r.foracid)
where r.cif_id = c_cust_id
order by r.foracid
, k.card_num;
begin
dbms_output.enable (buffer_size => null); -- enable dbms_output with size unlimited
open c_acct_card(core_cust_id);
loop
fetch c_acct_card
bulk collect
into acct_card_list
limit k_acct_card_buffer_limit;
for i in 1 .. acct_card_list.count
loop
dbms_output.put (acct_card_list(i).acct_num || ' ==> ');
if acct_card_list(i).card_num is not null
then
dbms_output.put_line (acct_card_list(i).card_num);
blokiraj_karticu (acct_card_list(i).card_num);
else
dbms_output.put_line ('No card for this account');
end if;
end loop;
-- exit buffer fetch when current buffeer is not full. As that means all rows
-- from cursor have been fetched/processed.
exit when acct_card_list.count < k_acct_card_buffer_limit;
end loop;
close c_acct_card;
end blokiraj_proc;
Well this is just another approach. If it's better for you, great. I also want to repeat and expand Ed Stevens warning of running this from a trigger. If either of the tables here is the table on which the trigger fired you will still get a mutating table exception - you cannot just hide it behind a procedure. And even if not its a lot of looping for trigger.

getting entire data into collections using oracle PLSQL Bulk collect with limit clause

I have one table called EMP with 140000 rows and I need , to keep entire data into collection .How to extend collection and load entire data into collection using "BULK COLLECT ..LIMIT" clause feature.
The below logic not providing required result , since data has been overridden with new records.Please suggest me the logic.
DECLARE
CURSOR c_get_employee IS
SELECT empno,
ename,
deptno,
sal
FROM emp;
TYPE t_employee
IS TABLE OF c_get_employee%ROWTYPE INDEX BY inary_integer;
l_employee T_EMPLOYEE;
BEGIN
OPEN c_get_employee;
LOOP
FETCH c_get_employee bulk collect INTO l_employee limit 300;
EXIT WHEN l_employee.count = 0;
END LOOP;
CLOSE c_get_employee;
FOR i IN 1..l_employee.count LOOP
dbms_output.Put_line (L_employee(i).ename
||'<-->'
||L_employee(i).sal);
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.Put_line ('Unexpected error :- '
|| SQLERRM);
END;
You are exiting the loop too early. You need stop the fetch loop after the for loop and close cursor after that.
Also, as #APC pointed out, the exit condition should use count of fetched results instead of NOTFOUND on cursor. Otherwise, if the last fetch has lesser records than the fetch size, the NOTFOUND will be true and loop terminates incorrectly.
Try this:
DECLARE
CURSOR c_get_employee IS
SELECT empno,
ename,
deptno,
sal
FROM emp;
TYPE t_employee
IS TABLE OF c_get_employee%ROWTYPE INDEX BY binary_integer;
l_employee T_EMPLOYEE;
BEGIN
OPEN c_get_employee;
LOOP
FETCH c_get_employee bulk collect INTO l_employee limit 3;
EXIT WHEN l_employee.count = 0;
FOR i IN 1..l_employee.count LOOP
dbms_output.Put_line (L_employee(i).ename
||'<-->'
||L_employee(i).sal);
END LOOP;
END LOOP;
CLOSE c_get_employee;
EXCEPTION
WHEN OTHERS THEN
dbms_output.Put_line ('Unexpected error :- '
|| SQLERRM);
END;
The below logic is not giving required result
Wild guess: you're only getting twelve rows. This is a familiar gotcha with LIMIT clause. This line is the problem:
EXIT WHEN c_get_employee%NOTFOUND;
You have fourteen records in EMP: The limit of 3 means you collect four sets of records. The last FETCH only collects 2 records. PL/SQL interprets this as NOTFOUND. The solution is to check the size of the collection:
EXIT WHEN l_employee.count() = 0;
I want to load entire data into collection and close the cursor.After that I want to open collection and use data for business logic
That's not how BULK COLLECT ... LIMIT works. The point of the LIMIT clause is to, er, limit the number of records fetched at a time. We need to do this when the queried data is too big to handle in a single fetch. PL/SQL collections are memory structures held in the session's allocation of memory: if they get too big they will blow the PGA. (Definition of "too big" will depend on how your DBA has configured the PGA.)
So, if you have a small result set, ditch the LIMIT clause and populate the collection in a single fetch. But if you have sufficient data to require the LIMIT clause you need to include the business logic loop inside the fetch loop.

What is more efficient? (SP PL/SQL)

I have a stored procedure which performs some transactions (insert / update) and want to know which of these two options run "COMMIT" more efficiently:
OPTION 1:
BEGIN
OPEN myCursor;
LOOP
FETCH myCursor INTO AUX_ID, AUX_VAR1, AUX_VAR2;
EXIT WHEN myCursor%NOTFOUND;
SELECT count(*) INTO myCount FROM myTable WHERE code = AUX_ID;
IF myCount > 0 THEN
UPDATE myTable
SET VAR1 = AUX_VAR1, VAR2 = AUX_VAR2
WHERE code = AUX_ID_BD;
COMMIT;
ELSE
INSERT INTO myTable(code, VAR1, VAR2)
VALUES(AUX_ID, AUX_VAR1, AUX_VAR2)
COMMIT;
END IF;
END LOOP;
CLOSE myCursor;
END;
OR OPTION 2:
BEGIN
OPEN myCursor;
LOOP
FETCH myCursor INTO AUX_ID, AUX_VAR1, AUX_VAR2;
EXIT WHEN myCursor%NOTFOUND;
SELECT count(*) INTO myCount FROM myTable WHERE code = AUX_ID;
IF myCount > 0 THEN
UPDATE myTable
SET VAR1 = AUX_VAR1, VAR2 = AUX_VAR2
WHERE code = AUX_ID_BD;
ELSE
INSERT INTO myTable(code, VAR1, VAR2)
VALUES(AUX_ID, AUX_VAR1, AUX_VAR2)
END IF;
END LOOP;
COMMIT;
CLOSE myCursor;
END;
it's okay? or is there a better way?
Option #2 is definitely more efficient, although it's hard to tell if it will be noticeable in your case.
Every COMMIT requires a small amount of physical I/O; Oracle must ensure all the data is written to disk, the system change number (SCN) is written to disk, and there are probably other consistency checks I'm not aware of. In practice, it takes a huge number of COMMITs from multiple users to significantly slow down a database. When that happens you may see unusual wait events involving REDO, control files, etc.
Before a COMMIT is issued, Oracle can make the changes in memory or asynchronously. This may allow the performance to be equivalent to an in-memory database.
An even better option is to avoid the issue entirely by using a single MERGE statement, as Sylvain Leroux suggested. If the processing must be done in PL/SQL, at least replace the OPEN/FETCH cursor syntax with a simpler cursor FOR-loop. A cursor FOR-loop will automatically bulk collect data, significantly improving read performance.

Cursor occasionally lose data when open in run time

This is my cursor with SELECT statement which always return right result. But when i use it in a procedure, it sometimes get wrong result (return only one row).
This problem appears with a large frequency (~5-10 times per 1000 procedure call). Maybe this is known issues with oracle 10gR2 or some disadvantages in my procedure.
I hope I can understand why this happening and get the better solutions because I get ~5000 procedure calls per day.
This is my cursor which declared in procedure:
CURSOR cur_result (var_num NUMBER)
IS
SELECT
m_username,
m_password
FROM
M_USERS
WHERE
m_status = 1
AND rownum <= var_num;
This is my procedure body:
p_num:=5;
-- the IF statement for guarantee the SELECT statement always
-- get more rows than p_num
LOCK TABLE M_USERS IN EXCLUSIVE MODE;
OPEN cur_result (p_num);
LOOP
FETCH
cur_result
INTO
p_username,
p_password;
-- the problems here: only 1 row is queried
-- the LOOP will exit after second time fetch
IF (cur_result%NOTFOUND AND (cur_result%ROWCOUNT < p_num) ) THEN
iserror := 1;
EXIT;
END IF;
EXIT WHEN cur_result%NOTFOUND;
UPDATE
M_USERS
SET
m_status = 2
WHERE
m_username = p_username
AND m_password = p_password;
END LOOP;
CLOSE cur_result;
Thanks in advance.

Oracle stored procedure cursor always returns zero rows

I have this cursor in a procedure in a package:
PROCEDURE CANCEL_INACTIVE(IN_DAYS_OLD NUMBER)
IS
CURSOR inactive IS
SELECT * FROM MY_TABLE
WHERE STATUS_CHANGED_DATE <= TRUNC(SYSDATE-IN_DAYS_OLD)
AND CANCEL_CD IS NULL;
rec inactive%ROWTYPE;
BEGIN
OPEN inactive;
LOOP
FETCH inactive INTO rec;
EXIT WHEN inactive%NOTFOUND;
-- do an update based on rec.id
END LOOP;
END;
END CANCEL_INACTIVE;
Every time I test or run the procedure, inactive always has zero rows. However, when I put the EXACT same query into a SQL window, I get the rows I'm looking for.
What the heck?
Probably you'are testing on noncommited data.
Or: you're not commiting your update based on rec.id.
Or: your update does nothing. (the where clause is not satisfied by any rows on target table)

Resources