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;
Related
A master table is to be updated daily with the input data from two sources in two tables. The plsql code for processing the two tables are practically identical except for the table names. We have to separately log possible errors about the data in the input tables therefore have to run the code once each for the two input tables.
The attempted solution is by putting the table names in a variable, and cycle through the code twice:
declare
input_table varchar2(20);
begin
for i in (select column_value as var from table(sys.ODCIvarchar2List('MIDGETS', 'GIANTS'))) loop
if i.var = 'MIDGETS' then
input_table := 'midget_table';
elsif i.var = 'GIANTS' then
input_table := 'giant_table';
end if;
for rec in (select col1, col2 from input_table) loop
<the processing code>
end loop;
end;
/
The problem is that plsql does not seem to be aware that input_table is a variable. It "thinks" that input_table is literally the name of the table, and returns the (ORA-00942: table or view does not exist) error.
Since this is dynamic code, the EXECUTE IMMEDIATE was then tried:
declare
input_table varchar2(20);
begin
for ... 'MIDGETS', 'GIANTS' ... loop
input_table := ...
...
end loop;
for rec in ( EXECUTE IMMEDIATE 'select col1, col2 from ' || input_table ) loop
<processing>
end loop;
end;
/
But EXECUTE IMMEDIATE is not allowed either in this context.
Is there a way at all? Or is making two copies of the .sql file, one for MIDGETS and one for GIANTS, the only way out?
You can use dynamic query as below
declare
type crs_type is ref cursor;
c crs_type;
v_query varchar2(2000);
input_table varchar2(20);
v_col1 midgets.col1%type; -- assuming identical data types for common named columns
v_col2 midgets.col2%type;
begin
for ... 'MIDGETS', 'GIANTS' ... loop
input_table := ...
...
end loop;
v_query := 'select col1, col2 from ' || input_table;
open c for v_query;
loop
fetch c into v_col1, v_col2;
exit when c%notfound;
<processing>
end loop;
close c;
end;
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;
in a regular query I can use the xmlexists function to look if a specific value is present in a xmltype column. But when I want to use it in a pl/sql block the script will not compile because of a syntax error (encountered the symbol "passing" when expecting one of the following...).
Simple script example:
DECLARE
v_xml xmltype;
BEGIN
for rec in (select xmltypecol from mytable where type='XXX')
loop
v_xml := rec.xmltypecol;
if xmlexists('/test[node=(10,12)]' passing v_xml) then
-- processing
end if;
end loop;
END;
What is the right way to use xmlexists in a pl/sql block ?
Thanks!
Some Oracle XML... functions can be used only in SQL but not in PL/SQL - don't ask me why.
For example v_xml := XMLELEMENT("number", 123); is not possible, you have to run SELECT XMLELEMENT("number", 123) INTO v_xml FROM dual;
Try this one:
DECLARE
v_xml xmltype;
r INTEGER;
BEGIN
for rec in (select xmltypecol from mytable where type='XXX')
loop
v_xml := rec.xmltypecol;
SELECT COUNT(*)
INTO r
FROM dual
WHERE xmlexists('/test[node=(10,12)]' passing v_xml);
if r = 1 then
-- processing
end if;
end loop;
END;
Inspired by Boneist answer, why are you not doing
DECLARE
v_xml xmltype;
BEGIN
for rec in (select xmltypecol from mytable where type='XXX' AND xmlexists('/test[node=(10,12)]' passing xmltypecol)
loop
v_xml := rec.xmltypecol;
-- processing
end loop;
END;
XMLTYPE has its own methods, one of which is existsnode. That means you can avoid the context switching between PL/SQL and SQL that you'd have to do if you wrapped the call in a select ... from dual where by using xmltype_variable.existsnode('<node>').
Your code would therefore look something like:
DECLARE
v_xml xmltype;
BEGIN
for rec in (select xmltypecol from mytable where type='XXX')
loop
v_xml := rec.xml;
if v_xml.xmlexists('/test[node=(10,12)]') = 1 then
-- processing
end if;
end loop;
END;
However, what is stopping you from doing the check in the cursor? If you're only going to do the processing on the rows which meet your condition, wouldn't it be better to do the filtering in the query?
Also, if your processing involves DML, you could perhaps use XMLTABLE to produce something you could join directly to the DML statement(s) which would allow the processing to be done all at once rather than row-by-row, thus negating the need for cursor-for-loop processing at all?
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
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;