Show custom message when no rows selected in procedure [duplicate] - oracle

I have a following stored procedure in which I have used a cursor. Depending on whether the cursor return any records or not I need to do some processing.
But I am not sure how to check if the cursor return any records.
CREATE OR REPLACE PROCEDURE SP_EMPLOYEE_LOOKUP_BY_EMP_ID
(
IN_USER_ID IN NUMBER,
IN_EMPLOYEE_ID NUMBER,
IN_HC_AS_ON_DATE VARCHAR2,
emp_cursor OUT SYS_REFCURSOR
)
IS
CURSOR employees IS
SELECT * FROM EMPLOYEE e;
BEGIN
if(record exist ) then
FOR employee IN employees
LOOP
// do something
END LOOP;
else if employees is empty then
// do something else
END;

It's not possible to check if the cursor returns records without opening it.
(see here)
So you can either have some fast query just to see if there are records (using count for example),
Or, you can do it like this:
CREATE OR REPLACE PROCEDURE SP_EMPLOYEE_LOOKUP_BY_EMP_ID
(
IN_USER_ID IN NUMBER,
IN_EMPLOYEE_ID NUMBER,
IN_HC_AS_ON_DATE VARCHAR2,
emp_cursor OUT SYS_REFCURSOR
)
IS
is_found_rec boolean := false;
CURSOR employees IS
SELECT * FROM EMPLOYEE e;
BEGIN
FOR employee IN employees
LOOP
is_found_rec := true;
// do something
END LOOP;
if not is_found_rec then
// do something else
end if;
END;

I think that it possible only with FETCH. Try to use
if myCursor%found then
// some body
end if;
But if somebody know another way so correct me.

I like this way:
DECLARE
CURSOR my_cur
IS
SELECT 'a' AS a FROM DUAL WHERE 1=1;
my_rec my_cur%rowtype;
BEGIN
OPEN my_cur;
LOOP
FETCH my_cur INTO my_rec;
IF my_cur%NOTFOUND
THEN
IF my_cur%ROWCOUNT=0
THEN
-- do stuff when cursor empty
DBMS_OUTPUT.PUT_LINE('NOTFOUND,RC=0' || '_' || my_cur%ROWCOUNT || '_' || my_rec.a);
END IF;
EXIT;
ELSE
-- do stuff when cursor not empty
DBMS_OUTPUT.PUT_LINE('FOUND,RC>0' || '_' || my_cur%ROWCOUNT || '_' || my_rec.a);
END IF;
END LOOP;
CLOSE MY_CUR;
END;
Output when cursor is 1=1 (not empty):
FOUND,RC>0_1_a
Output when cursor is 1=0 (empty):
NOTFOUND,RC=0_0_

I like to use the same select query that comprises the cursor and select the count it returns into a variable. You can then evaluate the variable to determine what to do next. For example you have a cursor like this:
CURSOR c_example IS
SELECT field1
,field2
,field3
FROM table1 a
WHERE a.field1 LIKE '%something%';
Before you open the cursor to do any processing, select cursor result count into a variable.
SELECT count(*)
INTO v_exampleCount
FROM table1 a
WHERE a.field1 LIKE '%something%';
Now, again before you open cursor, do something like:
IF exampleCount>0 THEN
FOR example IN c_example
LOOP
....
END LOOP; --end example loop
....
END IF; --end example if

341/5000
One solution would be to work with reverse logic. It is e.g. not possible to ask:
IF my_record IS NULL THEN
do something;
END IF;
or
IF my_record.my_attibute IS NULL THEN
do something;
END IF;
Instead it is possible to ask:
IF my_record.my_attibute IS NOT NULL THEN
go on processing;
ELSE
do something;
END IF;

Related

How to append rows to a existing SYS_REFCURSOR?

I want to know if there is a way append to the results of a cursor that is fetched from inside a loop.
Right now out_cursor contains the results pertaining to the last iteration of the outer for loop. I want to know if it will be possible to append the rows from each iteration to this cursor so that the cursor will contain rows from all iterations of the loop.
For context, this out_cursor is consumed by a Java DAO class.
CREATE OR REPLACE NONEDITIONABLE PROCEDURE testproc (out_cursor OUT SYS_REFCURSOR)
IS
CURSOR actor_cursor IS
SELECT actor_id, first_name, last_name
FROM actor
WHERE actor_id <= 100;
row1 actor_cursor%rowtype;
TYPE MyRec IS RECORD (actor_id film_actor.actor_id%TYPE, film_id film_actor.film_id%TYPE);
rec MyRec;
BEGIN
FOR row1 IN actor_cursor
LOOP
dbms_output.put_line('actor_id:'||row1.actor_id ||' ---- '|| row1.first_name || row1.last_name);
OPEN out_cursor FOR
SELECT actor_id, film_id
FROM film_actor
WHERE actor_id = row1.actor_id;
END LOOP;
LOOP -- this loop is here just to print out_cursor for testing
FETCH out_cursor INTO rec;
EXIT WHEN out_cursor%NOTFOUND;
dbms_output.put_line('actor_id:'||rec.actor_id||', film_id:'||rec.film_id);
END LOOP;
END;
The output of the script is something like this
actor_id:98 ---- CHRIS BRIDGES
actor_id:99 ---- JIM MOSTEL
actor_id:100 ---- SPENCER DEPP
actor_id:100, film_id:17
actor_id:100, film_id:118
I understand that only rows from the last iteration of the for loop are there in the out_cursor.
Is there any way to make out_cursor return the results from all the iterations of the outer FOR LOOP, essentially aggregate the results of all the iterations. To produce result like this?
actor_id:98 ---- CHRIS BRIDGES
actor_id:99 ---- JIM MOSTEL
actor_id:100 ---- SPENCER DEPP
actor_id:98, film_id:77 // results from iteration #1
actor_id:98, film_id:43
actor_id:99, film_id:67 // results from iteration #2
actor_id:99, film_id:90
actor_id:100, film_id:17 // results from iteration #3
actor_id:100, film_id:118
I am aware that I can easily archive the same results using JOINS. But I am not allowed to modify the SQL queries (actual SQLs are very complex) - I can only run them and use the results produced from one SQL as parameters (in SELECT or WHERE clause) for the next SQL.
(UPDATE)
The General requirement is - there are a sequence of queries, resultset of each is used in the constraints of the next one. I cannot modify the queries themselves - so using a join or sub-query is out of question.
I was hoping to open a cursor for each query and then iterate through the values in the cursor - using the cursor attributes in the constraint of the next query, for which I will open a cursor inside the for loop. Exactly as shown in the sample. But the problem was the cursor inside the loop only contained the selections made in the last iteration - during each iteration a new cursor was getting created. So, I am getting only a subset of the results I wanted in the out_cursor.
I think i found a way to do this -
CREATE OR REPLACE PACKAGE PKG_BIDS_REPORTS_TEST as
TYPE Q2DATA IS RECORD (
actor_id film_actor.actor_id%TYPE,
film_id film_actor.film_id%TYPE
);
TYPE Q2DATA_TAB IS TABLE OF Q2DATA INDEX BY BINARY_INTEGER;
Q2DATA_REC Q2DATA_TAB;
PROCEDURE pqr_reports_test(l_out_data OUT sys_refcursor);
end PKG_BIDS_REPORTS_TEST;
/
create or replace NONEDITIONABLE PACKAGE BODY PKG_BIDS_REPORTS_TEST as
PROCEDURE pqr_reports_test(l_out_data OUT sys_refcursor) AS
CURSOR actor_cursor IS
SELECT actor_id, first_name, last_name
FROM actor
WHERE actor_id <= 100;
row1 actor_cursor%rowtype;
CURSOR film_actor_cursor(actorid film_actor.actor_id%TYPE) IS
SELECT actor_id, film_id
FROM film_actor
WHERE actor_id = actorid;
row2 film_actor_cursor%rowtype;
V_CNT NUMBER:=0;
BEGIN
V_CNT := q2data_rec.COUNT;
FOR row1 IN actor_cursor
LOOP
--dbms_output.put_line(row1.actor_id ||' ---- '|| row1.first_name ||' ---- '|| row1.last_name);
FOR row2 IN film_actor_cursor(row1.actor_id)
LOOP
V_CNT := V_CNT + 1;
q2data_rec(V_CNT).actor_id := row2.actor_id;
q2data_rec(V_CNT).film_id := row2.film_id;
--dbms_output.put_line(row2.actor_id||','||row2.film_id);
END LOOP;
END LOOP;
OPEN L_OUT_DATA FOR SELECT DISTINCT datarec.actor_id, datarec.film_id from TABLE(q2data_rec) datarec;
END;
End PKG_BIDS_REPORTS_TEST;
Essentially create a PLSQL table and fill it in the loop with a counter like a Java array. It works. Not the most elegant, definitely.
So, I would definitely appreciate any optimizations or alternates.
In order to get the output you are expecting for, you cannot close the first cursor before open the second one. It is the only way to have both iterations combined.
Although it would be inefficient, a way would be
BEGIN
FOR row1 IN actor_cursor
LOOP
dbms_output.put_line(row1.actor_id ||' ---- '|| row1.first_name || row1.last_name);
OPEN out_cursor FOR
SELECT actor_id, film_id
FROM film_actor
WHERE actor_id = row1.actor_id;
END LOOP;
FOR row1 IN actor_cursor
LOOP
OPEN out_cursor FOR
SELECT actor_id, film_id
FROM film_actor
WHERE actor_id = row1.actor_id;
LOOP
FETCH out_cursor INTO rec;
EXIT WHEN out_cursor%NOTFOUND;
dbms_output.put_line(row1.actor_id ||','|| rec.film_id);
END LOOP;
END LOOP;
END;
As I indicated before you cannot combine the result of separate cursors. However, you can (at least in this case) combine them into a single select and return that result. And since you are returning a reference cursor you cannot loop through it unless you close and re-open it. So in this case your procedure should consist of a single open cursor statement.
create or replace
procedure testproc (out_cursor out sys_refcursor)
is
begin
open out_cursor for
select a.actor_id, a.first_name, a.last_name, fa.film_id
from actor a
join film_actor fa
on fa.actor_id = a.actor_id
where a.actor_id <= 100;
end testproc ;
------- test ------
declare
actor_id actor.actor_id%type
fname actor.first_name%type
l_name actor,last_name%type
film_id film.film_id%type;
rec sys_refcursor ;
begin
testproc(rec);
loop
fetch rec
into actor_id, fname, lname, film_id;
exit when rec%notfound;
dbms_output.put_line('actor_id:' || actor_id ||
' name: ' || fname || ' ' || lname
' film_id:' || film_id
);
end loop;
end ;

How to stop repeating of row using %type and cursor

i make a procedure as shown below i want to fetch cursor values and extract these values in a variables and extract these variable in a new loop but issue same row is repeated
CREATE OR REPLACE PROCEDURE FSC.TEST (TRIGER_BY VARCHAR2)
AS
CURSOR TO_HOD
IS
SELECT EMP.EMPLOYEE_CODE,
EMP.EMP_NAME
--APR.LEFT_DT,
-- APR.RESIGN_TYPE
FROM FSC_APPROVAL APR, CHR_ALL_EMPLOYEE_BI_V EMP
WHERE APR.HOD_APPR = 'Y'
AND APR.ZONE_HD_APPR IS NULL
AND EMP.EMPLOYEE_ID = APR.EMP_ID;
CURSOR TO_ZONE
IS
SELECT EMP.EMPLOYEE_CODE,
EMP.EMP_NAME
-- APR.LEFT_DT,
-- APR.RESIGN_TYPE
FROM FSC_APPROVAL APR, CHR_ALL_EMPLOYEE_BI_V EMP
WHERE APR.HOD_APPR = 'Y'
AND APR.ZONE_HD_APPR = 'Y'
AND APR.TIM_OFC_APPR IS NULL
AND EMP.EMPLOYEE_ID = APR.EMP_ID;
emp_code CHR_ALL_EMPLOYEE_BI_V.EMPLOYEE_CODE%type;
emp_name CHR_ALL_EMPLOYEE_BI_V.EMP_NAME%type;
BEGIN
IF TRIGER_BY = 'HOD'
THEN
OPEN TO_HOD;
LOOP
FETCH TO_HOD INTO emp_code,emp_name;
EXIT WHEN TO_HOD%NOTFOUND;
counter:=TO_HOD%ROWCOUNT;
END LOOP;
ELSIF TRIGER_BY = 'ZONE'
THEN
OPEN TO_ZONE;
LOOP
FETCH TO_ZONE INTO emp_code,emp_name;
EXIT WHEN TO_ZONE%NOTFOUND;
counter:=TO_ZONE%ROWCOUNT;
END LOOP;
END IF;
here i extract the data that i store in loop in above code
this loop extract the data
for i in 1..counter loop
DBMS_OUTPUT.PUT_LINE (emp_code||' '||emp_name);
--EXIT WHEN CUR_VAL is null;
end loop;
END;
/
but same row is repeated
emp_code and emp_name are scalars. That is, they only store one value. Each time you fetch data into them, you are overwriting the prior value. If you are trying to print the data at the end, emp_code and emp_name will only have the last values that were fetched and you'll repeat that many times.
Why are you using explicit cursors in the first place (which, incidentally, you've failed to close)? If, as I'm guessing, you're new to PL/SQL, you're better off using implicit cursors.
IF TRIGER_BY = 'HOD'
THEN
for rec in to_hod
loop
dbms_output.put_line( rec.employee_code || ' ' || rec.emp_name );
end loop;
ELSIF triger_by = 'ZONE'
THEN
for rec in to_zone
loop
dbms_output.put_line( rec.employee_code || ' ' || rec.emp_name );
end loop;
END IF;
Now, you could use implicit cursors, declare local collection types rather than local scalar variables, do a bulk collect from the cursor into that collection type, and then iterate through the collection in a subsequent step. But I am guessing that is more than you really want to do.

How to change the cursors dynamically in oracle?

I want to change the cursor in runtime dynamically.
PROCEDURE test_cur(a IN VARCHAR2,
b IN VARCHAR2)
IS
CURSOR cur_a IS
SELECT x.col_a,x.col_b FROM tab_a x, tab_b y
WHERE x.col_a = y.col_a
BEGIN
IF (condition) THEN
FOR rec IN cur_a LOOP
--DO SOME WORK
END LOOP;
ELSE
FOR rec IN cur_a LOOP
In this else section i want to add some other code in to the where clause of the cursor. I wanted to know is there any other way to do this?
why not create one cursor and put a part of the clause with your condition:
CURSOR cur_a IS
SELECT x.col_a,x.col_b FROM tab_a x, tab_b y
WHERE x.col_a = y.col_a
AND (condition AND (rest of your where clause));
So only one cursor and as many 'conditions' you need.
Know that Oracle will optimize the where clause to ignore all the parts where the condition if false (so no costs other than the compilation).
I used this technique in very complex search screens with Oracle. Works very well.
I hope that helps.
Christian
In this else section i want to add some other code in to the where clause of the cursor.
Then simply use a Cursor FOR loop thus you could use your desired query to loop.
For example,
IF (condition) THEN
FOR rec IN (SELECT .. FROM table_1 ..)
LOOP --
--DO SOME WORK
END LOOP;
ELSE
FOR rec IN (SELECT .. FROM table_2 ..)
LOOP
--DO SOME OTHER WORK
END LOOP;
Of course, another way is to use dynamic sql. Declare a varchar2 variable and assign the static part of the sql.
Inside each IF-ELSE part keep concatenating the required dynamic sql.
For example,
DECLARE
v_sql VARCHAR2(2000);
TYPE cur IS REF CURSOR;
v_cur cur;
BEGIN
v_sql := 'SELECT x.col_a,x.col_b FROM tab_a x, tab_b y WHERE x.col_a = y.col_a';
IF (condition)
THEN
v_sql := v_sql || ' AND <desired conditions>';
OPEN v_cur FOR v_sql;
LOOP
--DO SOME WORK
END LOOP;
ELSE
v_sql := v_sql || ' AND <other desired conditions>';
OPEN v_cur FOR v_sql;
LOOP
--DO SOME WORK
END LOOP;
Why don't you use Dynamic SQL for this if in case your where condition changes dynamically.
Declare the select queries you wanted along with various where conditions. Based on the conditions execute the corresponding sql
Refer the official documentation at below link
http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/dynamic.htm

How to check if cursor returns any records in oracle?

I have a following stored procedure in which I have used a cursor. Depending on whether the cursor return any records or not I need to do some processing.
But I am not sure how to check if the cursor return any records.
CREATE OR REPLACE PROCEDURE SP_EMPLOYEE_LOOKUP_BY_EMP_ID
(
IN_USER_ID IN NUMBER,
IN_EMPLOYEE_ID NUMBER,
IN_HC_AS_ON_DATE VARCHAR2,
emp_cursor OUT SYS_REFCURSOR
)
IS
CURSOR employees IS
SELECT * FROM EMPLOYEE e;
BEGIN
if(record exist ) then
FOR employee IN employees
LOOP
// do something
END LOOP;
else if employees is empty then
// do something else
END;
It's not possible to check if the cursor returns records without opening it.
(see here)
So you can either have some fast query just to see if there are records (using count for example),
Or, you can do it like this:
CREATE OR REPLACE PROCEDURE SP_EMPLOYEE_LOOKUP_BY_EMP_ID
(
IN_USER_ID IN NUMBER,
IN_EMPLOYEE_ID NUMBER,
IN_HC_AS_ON_DATE VARCHAR2,
emp_cursor OUT SYS_REFCURSOR
)
IS
is_found_rec boolean := false;
CURSOR employees IS
SELECT * FROM EMPLOYEE e;
BEGIN
FOR employee IN employees
LOOP
is_found_rec := true;
// do something
END LOOP;
if not is_found_rec then
// do something else
end if;
END;
I think that it possible only with FETCH. Try to use
if myCursor%found then
// some body
end if;
But if somebody know another way so correct me.
I like this way:
DECLARE
CURSOR my_cur
IS
SELECT 'a' AS a FROM DUAL WHERE 1=1;
my_rec my_cur%rowtype;
BEGIN
OPEN my_cur;
LOOP
FETCH my_cur INTO my_rec;
IF my_cur%NOTFOUND
THEN
IF my_cur%ROWCOUNT=0
THEN
-- do stuff when cursor empty
DBMS_OUTPUT.PUT_LINE('NOTFOUND,RC=0' || '_' || my_cur%ROWCOUNT || '_' || my_rec.a);
END IF;
EXIT;
ELSE
-- do stuff when cursor not empty
DBMS_OUTPUT.PUT_LINE('FOUND,RC>0' || '_' || my_cur%ROWCOUNT || '_' || my_rec.a);
END IF;
END LOOP;
CLOSE MY_CUR;
END;
Output when cursor is 1=1 (not empty):
FOUND,RC>0_1_a
Output when cursor is 1=0 (empty):
NOTFOUND,RC=0_0_
I like to use the same select query that comprises the cursor and select the count it returns into a variable. You can then evaluate the variable to determine what to do next. For example you have a cursor like this:
CURSOR c_example IS
SELECT field1
,field2
,field3
FROM table1 a
WHERE a.field1 LIKE '%something%';
Before you open the cursor to do any processing, select cursor result count into a variable.
SELECT count(*)
INTO v_exampleCount
FROM table1 a
WHERE a.field1 LIKE '%something%';
Now, again before you open cursor, do something like:
IF exampleCount>0 THEN
FOR example IN c_example
LOOP
....
END LOOP; --end example loop
....
END IF; --end example if
341/5000
One solution would be to work with reverse logic. It is e.g. not possible to ask:
IF my_record IS NULL THEN
do something;
END IF;
or
IF my_record.my_attibute IS NULL THEN
do something;
END IF;
Instead it is possible to ask:
IF my_record.my_attibute IS NOT NULL THEN
go on processing;
ELSE
do something;
END IF;

How to set cursor field name from another cursor? Oracle

I need to take cursor fields names from another cursor like this:
FOR rec1 IN (SELECT * FROM table1) LOOP
FOR rec2 IN (SELECT * FROM table2) LOOP
IF rec1.[rec2.field_name] <> '*' THEN
...
END IF;
END LOOP;
END LOOP;
Oracle is really not designed for this kind of behavior. The only way I can think of to achieve this is to use dynamic PL/SQL to produce the functionality you're looking for:
declare
v_field_value varchar2(2000);
begin
FOR rec1 IN (SELECT * FROM table1) LOOP
FOR rec2 IN (SELECT * FROM table2) LOOP
EXECUTE IMMEDIATE 'begin :value := :rec1.'
|| :rec2.field_name || '; end;'
USING OUT v_field_value, IN rec1;
IF v_field_value <> '*' THEN
...
END IF;
END LOOP;
END LOOP;
end;
However, just because this approach can work doesn't mean you should use it. If your field is not a string, for instance, Oracle will implicitly convert the value, which may result in a different value than what you expect. If this were my code, I would only use this as a last resort, after considering implementing the same functionality outside the database and redesigning the database's structure to avoid the need for this type of code.
Based on the comment the error mentioned in the comment, I've modified the code so to pass the records in using bind variables.
I am not sure, I understand what your problem is, you can access the outer cursor-loop's record just like you would expect from a variable that is declared in a scope above the current one.
for rec1 in (SELECT * FROM table1) loop
for rec2 in (SELECT * FROM table2) loop
if rec1.field = 1 and rec2.field_name <> '*' then
...
end if;
end loop;
end loop;
kinda like
declare
i Integer;
begin
declare
x Integer;
begin
i := x;
end;
end;

Resources