Oracle stored procedure cursor always returns zero rows - oracle

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)

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.

PLSQL IMPLICIT CURSOR No Data Found After CURSOR

I have a Main cursor that is working fine.
declare
v_firm_id number;
amount number;
v_total_sum TABLE_TEMP.TOTAL_SUM%TYPE;
CURSOR MT_CURSOR IS
SELECT firm_id FROM t_firm;
BEGIN
OPEN MT_CURSOR;
LOOP
FETCH MT_CURSOR INTO v_firm_id;
EXIT WHEN MT_CURSOR%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(to_char(sysdate, 'mi:ss') ||'--- '|| v_firm_id)
INSERT INTO TABLE_TEMP(TOTAL_SUM) VALUES(v_firm_id)
COMMIT;
END LOOP;
DBMS_LOCK.SLEEP(20);
BEGIN
FOR loop_emp IN
(SELECT TOTAL_SUM INTO v_total_sum FROM TABLE_TEMP)
LOOP
dbms_output.put_line(to_char(sysdate, 'mi:ss') ||'--- '|| v_total_sum || '-TEST--');
END LOOP loop_emp;
END;
end;
Everything Works fine except dbms_output.put_line(v_total_sum || '---');
I do not get any data there. I get the correct number of rows. which it inserted.
The problem is the cursor FOR loop has a redundant into clause which it appears the compiler silently ignores, and so v_total_sum is never used.
Try this:
begin
for r in (
select firm_id from t_firm
)
loop
insert into table_temp (total_sum) values (r.firm_id);
end loop;
dbms_lock.sleep(20);
for r in (
select total_sum from table_temp
)
loop
dbms_output.put_line(r.total_sum || '---');
end loop;
commit;
end;
If this had been a stored procedure rather than an anonymous block and you had PL/SQL compiler warnings enabled with alter session set plsql_warnings = 'ENABLE:ALL'; (or the equivalent preference setting in your IDE) then you would have seen:
PLW-05016: INTO clause should not be specified here
I also moved the commit to the end so you only commit once.
To summarise the comments below, the Cursor FOR loop construction declares, opens, fetches and closes the cursor for you, and is potentially faster because it fetches in batches of 100 (or similar - I haven't tested in recent versions). Simpler code has less chance of bugs and is easier to maintain in the future, for example if you need to add a column to the cursor.
Note the original version had:
for loop_emp in (...)
loop
...
end loop loop_emp;
This is misleading because loop_emp is the name of the record, not the cursor or the loop. The compiler is ignoring the text after end loop although really it should at least warn you. If you wanted to name the loop, you would use a label like <<LOOP_EMP>> above it. (I always name my loop records r, similar to the i you often see used in numeric loops.)

How to check whether a dynamic cursor will retrieve no records?

I have a procedure which has a structure as given below:
PROCEDURE broker(prm_qgent in varchar2, prm_cursor out sys_refcursor)
IS
mmy_query varchar(200);
BEGIN
OPEN prm_cursor FOR SELECT * FROM DUAL;
mmy_query :='SELECT *some dynamic query* where 1=1';
if prm_agent is not null then
mmy_query := mmy_query ||'AND agent_code = ''' ||prm_agent || '''';
end if;
OPEN prm_cursor FOR mmy_query;
END broker;
mmy_query is the search criteria. So if a broker is not in the table, it should retrieve zero records.
mmy_query is the dynamic query to retrieve records. If dynamic query retrieves record, it will only retrieve 1 record. So, I want to check if mmy_query retrieves no records.
tried prm_cursor%ROWTYPE, shows zero record in both cases.
tried SQL%ROWTYPE, always shows 1 record.
You need to open the cursor, fetch the first record from it and check FOUND or NOT_FOUND attributtes.
Note: if the first row is fetched, then there is no other way to "rewind the cursor to the beginning" except that closing that cursor and opening it again (and executing the same query again).
According to the documentation (from the above link), each named cursor has 4 attributtes:
%ISOPEN
named_cursor%ISOPEN has the value TRUE if the cursor is open, and
FALSE if it is not open.
%FOUND
named_cursor%FOUND has one of these values:
If the cursor is not open, INVALID_CURSOR
If cursor is open but no fetch was tried, NULL.
If the most recent fetch returned a row, TRUE.
If the most recent fetch did not return a row, FALSE.
%NOTFOUND
named_cursor%NOTFOUND has one of these values:
If cursor is not open, INVALID_CURSOR.
If cursor is open but no fetch was tried, NULL.
If the most recent fetch returned a row, FALSE.
If the most recent fetch did not return a row, TRUE.
%ROWCOUNT
named_cursor%ROWCOUNT has one of these values:
If cursor is not open, INVALID_CURSOR.
If cursor is open, the number of rows fetched so far.
As you see, there is no way to check whether the query returned either no rows or some rows whithout opening the cursor and fetching from it.
Simple examples:
A query that returns some rows:
DECLARE
my_cursor SYS_REFCURSOR;
y VARCHAR(10);
BEGIN
OPEN my_cursor FOR 'select * FROM dual WHERE 1=1';
FETCH my_cursor INTO y;
IF my_cursor%found THEN
DBMS_OUTPUT.PUT_LINE('FOUND');
ELSE
DBMS_OUTPUT.PUT_LINE('NOT FOUND');
END IF;
CLOSE my_cursor;
END;
/
Result: FOUND
A query that returns empty resultset:
DECLARE
my_cursor SYS_REFCURSOR;
y VARCHAR(10);
BEGIN
OPEN my_cursor FOR 'select * FROM dual WHERE 1=0';
FETCH my_cursor INTO y;
IF my_cursor%found THEN
DBMS_OUTPUT.PUT_LINE('FOUND');
ELSE
DBMS_OUTPUT.PUT_LINE('NOT FOUND');
END IF;
CLOSE my_cursor;
END;
/
Result: NOT FOUND
It is unknown whether or not rows will be found until the caller attempts to fetch from the cursor. At the time when the cursor is opened, this has not happened yet. A procedure that simply opens and returns a cursor has no way of knowing what will happen when the caller tries to fetch from it after the procedure has completed.
Perhaps it would be theoretically possible to parse the execution plan from v$sql_plan to get the estimated cardinality, if you could find its sql_id (perhaps from querying v$sql), but this would not be straightforward and at best the result would be a guess.
All you can really do is execute the query twice (or execute a simplified version of it, if that's possible), bearing in mind that the results could change between executions if other sessions are modifying the data.

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.

How can I find the number of records in an Oracle PL/SQL cursor?

Here's my cursor:
CURSOR C1 IS SELECT * FROM MY_TABLE WHERE SALARY < 50000 FOR UPDATE;
I immediately open the cursor in order to lock these records for the duration of my procedure.
I want to raise an application error in the event that there are < 2 records in my cursor. Using the C1%ROWCOUNT property fails because it only counts the number which have been fetched thus far.
What is the best pattern for this use case? Do I need to create a dummy MY_TABLE%ROWTYPE variable and then loop through the cursor to fetch them out and keep a count, or is there a simpler way? If this is the way to do it, will fetching all rows in my cursor implicitly close it, thus unlocking those rows, or will it stay open until I explicitly close it even if I've fetched them all?
I need to make sure the cursor stays open for a variety of other tasks beyond this count.
NB: i just reread you question.. and you want to fail if there is ONLY 1 record..
i'll post a new update in a moment..
lets start here..
From Oracle® Database PL/SQL User's Guide and Reference
10g Release 2 (10.2)
Part Number B14261-01
reference
All rows are locked when you open the cursor, not as they are fetched. The rows are unlocked when you commit or roll back the transaction. Since the rows are no longer locked, you cannot fetch from a FOR UPDATE cursor after a commit.
so you do not need to worry about the records unlocking.
so try this..
declare
CURSOR mytable_cur IS SELECT * FROM MY_TABLE WHERE SALARY < 50000 FOR UPDATE;
TYPE mytable_tt IS TABLE OF mytable_cur %ROWTYPE
INDEX BY PLS_INTEGER;
l_my_table_recs mytable_tt;
l_totalcount NUMBER;
begin
OPEN mytable_cur ;
l_totalcount := 0;
LOOP
FETCH mytable_cur
BULK COLLECT INTO l_my_table_recs LIMIT 100;
l_totalcount := l_totalcount + NVL(l_my_table_recs.COUNT,0);
--this is the check for only 1 row..
EXIT WHEN l_totalcount < 2;
FOR indx IN 1 .. l_my_table_recs.COUNT
LOOP
--process each record.. via l_my_table_recs (indx)
END LOOP;
EXIT WHEN mytable_cur%NOTFOUND;
END LOOP;
CLOSE mytable_cur ;
end;
ALTERNATE ANSWER
I read you answer backwards to start and thought you wanted to exit if there was MORE then 1 row.. not exactly one.. so here is my previous answer.
2 simple ways to check for ONLY 1 record.
Option 1 - Explicit Fetchs
declare
CURSOR C1 IS SELECT * FROM MY_TABLE WHERE SALARY < 50000 FOR UPDATE;
l_my_table_rec C1%rowtype;
l_my_table_rec2 C1%rowtype;
begin
open C1;
fetch c1 into l_my_table_rec;
if c1%NOTFOUND then
--no data found
end if;
fetch c1 into l_my_table_rec2;
if c1%FOUND THEN
--i have more then 1 row
end if;
close c1;
-- processing logic
end;
I hope you get the idea.
Option 2 - Exception Catching
declare
CURSOR C1 IS SELECT * FROM MY_TABLE WHERE SALARY < 50000 FOR UPDATE;
l_my_table_rec C1%rowtype;
begin
begin
select *
from my_table
into l_my_table_rec
where salary < 50000
for update;
exception
when too_many_rows then
-- handle the exception where more than one row is returned
when no_data_found then
-- handle the exception where no rows are returned
when others then raise;
end;
-- processing logic
end;
Additionally
Remember: with an explicit cursor.. you can %TYPE your variable off the cursor record rather then the original table.
this is especially useful when you have joins in your query.
Also, rememebr you can update the rows in the table with an
UPDATE table_name
SET set_clause
WHERE CURRENT OF cursor_name;
type statement, but I that will only work if you haven't 'fetched' the 2nd row..
for some more information about cursor FOR loops.. try
Here
If you're looking to fail whenver you have more than 1 row returned, try this:
declare
l_my_table_rec my_table%rowtype;
begin
begin
select *
from my_table
into l_my_table_rec
where salary < 50000
for update;
exception
when too_many_rows then
-- handle the exception where more than one row is returned
when no_data_found then
-- handle the exception where no rows are returned
when others then raise;
end;
-- processing logic
end;
If this is the way to do it, will
fetching all rows in my cursor
implicitly close it, thus unlocking
those rows
The locks will be present for the duration of the transaction (ie until you do a commit or rollback) irrespective of when (or whether) you close the cursor.
I'd go for
declare
CURSOR C1 IS SELECT * FROM MY_TABLE WHERE SALARY < 50000 FOR UPDATE;;
v_1 c1%rowtype;
v_cnt number;
begin
open c_1;
select count(*) into v_cnt FROM MY_TABLE WHERE SALARY < 50000 and rownum < 3;
if v_cnt < 2 then
raise_application_error(-20001,'...');
end if;
--other processing
close c_1;
end;
There's a very small chance that, between the time the cursor is opened (locking rows) and the select count, someone inserts one or more rows into the table with a salary under 50000. In that case the application error would be raised but the cursor would only process the rows present when the cursor was opened. If that is a worry, at the end do another check on c_1%rowcount and, if that problem was experienced, you'd need to rollback to a savepoint.
Create a savepoint before you iterate through the cursor and then use a partial rollback when you find there are < 2 records returned.
You can start transaction and check if SELECT COUNT(*) MY_TABLE WHERE SALARY < 50000 greater than 1.

Resources