How to remove a row from an Oracle cursor but not the database table - oracle

I have a FOR LOOP cursor and while looping through it I need to delete some of the rows from the cursor but not from the database table. Is that possible to do?
What I am trying to accomplish is to be left with only those rows in the cursor my code did not process by removing each processed row that met certain criteria

Bulk collect the cursor rows into a collection. Then as each row in processed delete it from the collection. What's left will be the rows not originally processed. The following provides a skeleton for the process needed:
declare
cursor c_cursor
is
select ... ;
type c_cursor_t is table of c_cursor%rowtype;
l_cursor_data c_cursor_t;
l_cursor_indx integer;
begin
open c_cursor;
fetch c_cursor
bulk collect
into l_cursor_data;
close c_cursor;
l_cursor_indx := l_cursor_data.first; -- set collection index to 1st value
while l_cursor_indx is not null
loop
if <row should be processed> -- determine which rows to process
then
<process_row>; -- and process them
l_cursor_data.delete(l_cursor_indx); -- then delete processed rows
end if ;
l_cursor_indx := l_cursor_data.next(l_cursor_indx); -- set collection index to next row or null if no morw rows.
end loop;
--- Handle anything left in l_cursor_data collection as they have not been processed.
--- THE SAME LOOP STRUCTURE AN BE USED FOR THE COLLECTION IF NEEDED.
end ;
Of course as #MT0 it would be much easier to eliminate those that will not be processed from the query to begin with. Just retrieve the rows you want to process is always the best practice.

You cannot modify a cursor while you are reading it; if you want to exclude rows then you will need to do it when you are generating the cursor.
Use the WHERE clause to exclude the rows from the cursor:
DECLARE
OPEN cursor_name FOR
SELECT *
FROM my_table
WHERE primary_key_column NOT IN ( 1, 2, 3 ); -- criteria to exclude.
BEGIN
-- process cursor with excluded rows.
END;
/

Load a collection with the results from your query, making sure the collection contains a 'processed' flag that is initialized to False. Then loop through the collection, processing as you need to. Flip the flag to True when done.
Then you could loop through the collection again where the processed_flag is False to get your untouched rows.

Related

How to remove data from or empty a cursor in PLSQL Oracle Procedure?

I want to fetch some rows from a table into a cursor and after doing my desired work,I want to empty the cursor so that I can use the cursor again. the whole process will be inside an loop. I have searched for this many times but I am not getting my desired post. I think I have made myself clear what do I want. In short, I want to treat my cursor as an ArrayList in Java.
As others have stated you need to fetch your cursor into a collection (or don't use an explicit cursor and BULK COLLECT directly with INTO (see below). Once you have it in a collection you can iterate through and delete entries that meet some requirement. PL/SQL collections have first and next functions that work together like an Iterator in Java so you can delete entries as you loop through them.
DECLARE
-- table definition
TYPE t_obj_tab IS TABLE OF all_objects%ROWTYPE;
v_obj_tab t_obj_tab;
-- local variables
v_index INTEGER;
BEGIN
-- get some data into a collection
SELECT *
BULK COLLECT
INTO v_obj_tab
FROM all_objects
WHERE rownum <= 500;
-- log the count for demonstration
dbms_output.put_line(v_obj_tab.count);
-- get the index of the first element in the collection
v_index := v_obj_tab.first;
-- loop through the collection
WHILE v_index IS NOT NULL
LOOP
-- delete objects with some criteria
IF v_obj_tab(v_index).object_name LIKE '%E%' THEN
v_obj_tab.delete(v_index);
END IF;
-- get next element of collection
v_index := v_obj_tab.next(v_index);
END LOOP;
-- log the count to see if any were deleted
dbms_output.put_line(v_obj_tab.count);
END;
/
-- 500
-- 206
You should be aware that pulling a large amount of objects into a collection will use a proportional amount of memory. PL/SQL collections should aim to be small when possible (personally I like 500 rows for bulk processing, but you should benchmark). It would be advisable to, instead of pulling data and filtering it procedurally, use SQL to filter the results to only return the rows you are interested in (as others also mentioned).
I think you were also asking how to process the same set of data multiple times. If you want to do that you can make a second collection variable, set it to the first (which makes a copy) and keep that as the original set. When you are done processing you can use that to get the original rows and do some different processing to them.
DECLARE
-- table definition
TYPE t_obj_tab IS TABLE OF all_objects%ROWTYPE;
v_obj_tab t_obj_tab;
v_obj_tab_2 t_obj_tab;
-- local variables
v_index INTEGER;
BEGIN
-- get some data into a collection
SELECT *
BULK COLLECT
INTO v_obj_tab
FROM all_objects
WHERE rownum <= 500;
-- copy!
v_obj_tab_2 := v_obj_tab;
-- log the count for demonstration
dbms_output.put_line(v_obj_tab.count);
-- get the index of the first element in the collection
v_index := v_obj_tab.first;
-- loop through the collection
WHILE v_index IS NOT NULL
LOOP
-- delete objects with some criteria
IF v_obj_tab(v_index).object_name LIKE '%E%' THEN
v_obj_tab.delete(v_index);
END IF;
-- get next element of collection
v_index := v_obj_tab.next(v_index);
END LOOP;
-- log the count to see if any were deleted
dbms_output.put_line(v_obj_tab.count);
dbms_output.put_line(v_obj_tab_2.count);
END;
/
-- 500
-- 206
-- 500 (copy count)

PL SQL Compund Triggers on Batch Inserts

I've written a compound trigger to fire on inserts. Multiple inserts are batched together and sent to the DB where the compound trigger picks it up. My problem is that i need to perform an update query on the same table for certain inserts depending on the data provided by the query. I can't run a row level action since that would result in a mutating trigger table error (ORA-4091). Best thing i could think of was to have the update query in the before or after statement blocks. i cannot have it on the before statement block since each update is dependent on individual inserts and there's no way of knowing the values before actually reaching that query. so i created a "Type" table and updated it before each row is modified and then later at the after statement block i iterate through the Type table and perform update queries using the data on the table. No matter what i tried the After statement block will only perform update queries for the last insert only.
TYPE apple IS RECORD ( v_size apple_t.size%Type, v_color apple_t.color%Type);
TYPE t_apple IS TABLE OF apple INDEX BY VARCHAR2(20);
BEFORE ROW
t_apple(key).v_size := :New.size;
t_apple(key).v_color := :New.color;
END BEFORE ROW
AFTER STATEMENT
Iterator := t_apple.First;
LOOP EXIT WHEN ITERATOR IS NULL;
UPDATE apple_t SET SIZE = 10
WHERE color = t_apple(Iterator).color;
Iterator := t_apple.Next(Iterator);
END LOOP
END AFTER STATEMENT
This basically is how the trigger is designed. Using a second table is out of the question since trigger cost is a major factor. Any Pointers? Please and Thankyou
I dont fully understand but I think you can get your keys after each row ,then update data in after statament block as follows.
declare
idx number := 1 ;
type array_t is varray(10000) of varchar2(100) ;
colorArr array_t := array_t();
AFTER EACH ROW IS
BEGIN
if inserting then
colorArr (idx) := :new.color;
idx := idx + 1 ;
end if;
END
AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
for i in 1..sicilNoCol.count
loop
-- update here
end loop;
END AFTER STATEMENT;
or why dont you write a simple before insert trigger that you can manuplate :new.size in it? Does it give table mutable error?

PLSQL: Fetching cursor rowcount and add to an array based on count

I have a cursor which fetches records from table based on filename (filenames are passed from array). now if filename is present more than once in table i need to add the filename to duparray such many number of times.
For example, if test2.txt is present 2 times and test3.txt is present 3 times, i need to have duparray as
{test2.txt,test2.txt,test3.txt,test3.txt,test3.txt}
But as per below code, duparray is coming as
{test2.txt,test3.txt,test3.txt} since i am having ROWCOUNT>1 check.
If that check is not there, filename which is present single time in table also gets added to it. Please advise where should i correct it.
CURSOR duplicateData IS
SELECT file_name from tablename where file_name=p_filearray(i)
dupRow duplicateData%rowtype;
Inside the procedure:
OPEN duplicateData ;
loop
fetch duplicateData INTO dupRow;
EXIT WHEN duplicateData %NOTFOUND;
IF duplicateData %ROWCOUNT >1
THEN
p_duparray.EXTEND;
p_duparray(p_duparray.LAST):=dupRow.file_name;
END IF;
end loop;
CLOSE duplicateData ;
Bob's First Law Of Database Programming states:
NEVER USE A LOOP TO DO A SELECT'S JOB
In this case you can use
DECLARE
FILENAME_COL_TYPE AS TABLE OF TABLENAME.FILENAME%TYPE INDEX BY PLS_INTEGER;
colFile_names ROW_COL_TYPE;
BEGIN
SELECT FILE_NAME
BULK COLLECT INTO colFile_names
FROM TABLENAME
ORDER BY FILE_NAME;
END;
This doesn't address the issue of the desired filenames already being in a collection, but presumably that collection of filenames was derived from a SELECT statement, so the criteria for choosing the appropriate filenames can be included in the above.
Loops are bad. NO LOOPS! Never! (What, never?) No, never! (Never?) Well, hardly ever...
:-)
I would suggest to collect into cursor file_name and number of occurances (cnt)
CURSOR duplicateData IS
select file_name,count(*) cnt from tablename where file_name=p_filearray(i)
group by file_name;
dupRow duplicateData%rowtype;
i number:=0;
An then use for loop to fill the array ...
OPEN duplicateData ;
loop
fetch duplicateData INTO dupRow;
EXIT WHEN duplicateData %NOTFOUND;
for i in 1..dupRow.cnt loop
p_duparray.EXTEND;
p_duparray(p_duparray.LAST):=dupRow.file_name;
end loop;
CLOSE duplicateData ;
Perhaps I'm missing something, but it looks as though you do nothing for the first loop iteration due to the IF duplicateData%ROWCOUNT > 1, and this is why you're losing the first value.
I'd automatically refactor the open-fetch-exit-end-close loop into the simple form:
for r in (
select file_name from tablename where file_name=p_filearray(i)
)
loop
p_duparray.extend;
p_duparray(p_duparray.last) := r.file_name;
end loop;
Although as Bob Jarvis mentioned you don't even need a loop just to fetch cursor results into an array, when you can just bulk collect it.
Depending on what you need the array for, it might be possible to make it an associative array of counts indexed by the filename. That is, store each filename only once and maintain the count for each one, so that filecounts('test3.txt') = 3, rather than storing test3.txt three times.

Oracle Cursor with getting data from each row

I am new in programming. I want to use procedure to get all the substr in address and put it in my new column 'info'. My procedure now only get the first row of string from address and place it all over the 'info' column. Am I need a loop or something to make my procedure get the right data from each row?
create or replace PROCEDURE update_data
IS
data_1 VARCHAR2(13 CHAR);
CURSOR c1 IS
SELECT substr(t.address, 3, 4)
FROM table_1 t
WHERE t.age > 21
BEGIN
OPEN c1;
fetch c1 INTO data_1;
Update table_1 t
SET t.info = data_1;
CLOSE c1;
END;
Thanks for helping me out!!
One major problem is that you're not limiting your UPDATE statement. You're actually updating EVERY ROW in the entire table. Watch out for that whenever you're writing UPDATE and DELETE statements or you will cause major problems and your co-workers won't thank you. I would suggest you write all of these types of statements as SELECT statements for to ensure that you're properly limiting with the WHERE clause and then convert it to a more dangerous statement.
As for your specific question - yes - you need to loop through every record in the cursor and then update the SINGLE corresponding row in the table.
Here's an updated example:
CREATE OR REPLACE PROCEDURE update_data
IS
CURSOR c1
IS
SELECT primary_key_column, /* SELECT THIS SO THAT YOU CAN IDENTIFY THE SINGLE ROW TO UPDATE */
SUBSTR(t.address, 3, 4) AS info /*NAME THIS SO IT CAN BE REFERENCED IN THE LOOP */
FROM table_1 t
WHERE t.age > 21;
BEGIN
FOR r /* NAME THE ROW */ IN c1 LOOP /* ORACLE AUTMOATICALLY OPENS THE CURSOR HERE */
UPDATE table_1 t
SET t.info = r.info
WHERE t.primary_key_column = r.primary_key_column; /* LIMIT THE UPDATE TO THIS ROW ONLY */
END LOOP; /* ORACLE AUTOMATICALLY CLOSES THE CURSOR HERE */
END;
You could use the OPEN, FETCH, CLOSE method you are currently using with a loop, but I personally think the FOR ... IN syntax is more readable, easier to write, and handles opening and closing cursors for you, so I've used it here.
The main thing to notice is that the UPDATE is now limited to one single record, so that every iteration through the loop will update the same record that you're running SUBSTR on - and NOT the entire table.
Alternatively, this could all be done with a single statement:
UPDATE table_1 t
SET t.info = SUBSTR(t.address, 3, 4)
WHERE t.age > 21;
Just make sure you're properly limiting your UPDATE.

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