Reputation: 13
Im trying to select some rows from a Table in ORACLE and at the same time update the selected rows state. I found a way to do so with a stored function and Cursors but I cant manage to return the rows after using the cursor to update. This is my code:
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
CURSOR c_operations IS
SELECT * FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
BEGIN
FOR r_operation IN c_operations
LOOP
UPDATE
TABLE1
SET
TABLE1.STATUS = 'OK'
WHERE
TABLE1.ID_TABLE1 = r_operation.ID_TABLE1;
END LOOP;
COMMIT;
-- Missing conversion from cursor to sys_refcursor
RETURN l_return;
END;
The update is working but Im still missing how to return the updated rows that are in the cursor (c_operations ).
Thank you.
Upvotes: 1
Views: 1817
Reputation: 14936
Instead of a loop to update how about a bulk update collecting the updated ids. Then a table function from those returned ids.
create type t_table1_id is
table of integer;
create or replace function set_table1_status_ok
return sys_refcursor
is
l_results_cursor sys_refcursor;
l_updated_ids t_table1_id;
begin
update table1
set status = 'Ok'
where status != 'Ok'
returning table1.id
bulk collect
into l_updated_ids;
open l_results_cursor for
select *
from table1
where id in (select * from table(l_updated_ids));
return l_results_cursor;
end set_table1_status_ok;
-- test
declare
updated_ids sys_refcursor;
l_this_rec table1%rowtype;
begin
updated_ids := set_table1_status_ok();
loop
fetch updated_ids into l_this_rec;
exit when updated_ids%notfound;
dbms_output.put_line ( l_this_rec.id || ' updated.');
end loop;
close updated_ids;
end ;
Upvotes: 0
Reputation: 146269
I'm going to make some assumptions:
id_table1
is the primary key of the table, so your RBAR (*) update affects only one rowid_table1
is numericIf these assumptions are wrong you will need to tweak the following code.
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
l_id table1.id_table1%type;
l_upd_ids sys.odcinumberlist := new sys.odcinumberlist();
CURSOR c_operations IS
SELECT * FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
BEGIN
FOR r_operation IN c_operations LOOP
UPDATE TABLE1
SET TABLE1.STATUS = 'OK'
WHERE TABLE1.ID_TABLE1 = r_operation.ID_TABLE1
returning TABLE1.ID_TABLE1 into l_id;
l_upd_ids.extend();
l_upd_ids(l_upd_ids.count()) := l_id;
END LOOP;
COMMIT;
open l_return for
select * from table(l_upd_ids);
RETURN l_return;
END;
The key points of the solution.
sys.odcinumberlist
to store the updated IDs;id_table1
value for the updated row;table()
function to casrt the collection into a table which can be queried in the ref cursor.This last point is why I chose to use sys.odcinumberlist
rather than defining a collection in the procedure. It's a SQL type, so we can use it in SELECT statements.
(*) Row-by-agonizing-row. Updating single records in a PL/SQL loop is the slowest way of executing bulk updates, and normally constitutes an anti-pattern. A straightforward set-based UPDATE should suffice. However, you know your own situation so I'm going to leave that as it is.
Upvotes: 2
Reputation: 50047
It looks to me like you don't need the initial cursor, since you're changing the STATUS of every row which is not 'OK' to 'OK', so you can do this is a simple UPDATE statement. Then use an OPEN...FOR
statement to return a cursor of all rows where STATUS is not 'OK'
, which shouldn't return anything because you've already changed all the status values to 'OK'
. I suggest that you rewrite your procedure as:
CREATE OR REPLACE FUNCTION FUNCTION_NAME
RETURN SYS_REFCURSOR
IS
l_return SYS_REFCURSOR;
BEGIN
UPDATE TABLE1
SET STATUS = 'OK'
WHERE STATUS != 'OK';
COMMIT;
OPEN l_return FOR SELECT *
FROM TABLE1
WHERE STATUS != 'OK'
FOR UPDATE OF TABLE1.STATUS;
RETURN l_return;
END;
Upvotes: 1