Oracle xmlexists in pl/sql block - oracle

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?

Related

Loop through plsql code for a number of tables

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;

How to store a column of result of select query in an array?

If we have a column in a table of type number, how can we store the result of select query on that column in an array ?
This sample uses a list (table of numbers) to achieve this, because i find
those lists much more handy:
CREATE OR REPLACE TYPE numberlist AS TABLE OF NUMBER;
DECLARE
v_numberlist numberlist;
BEGIN
SELECT intval numbercolumn
BULK COLLECT INTO v_numberlist
FROM lookup;
FOR i IN 1..v_numberlist.count
LOOP
dbms_output.put_line( v_numberlist(i) );
END LOOP;
END;
Create a type which store number:-
CREATE OR REPLACE TYPE varray is table of number;
--write your select query inside for loop () where i am extracting through level
declare
p varray := varray();
BEGIN
for i in (select level from dual connect by level <= 10) loop
p.extend;
p(p.count) := i.level;
end loop;
for xarr in (select column_value from table(cast(p as varray))) loop
dbms_output.put_line(xarr.column_value);
end loop;
END;
output:-
1
2
3
4
5
6
7
8
9
10
Just an option to use some native SQL datatype. Hope it helps.
SET SERVEROUTPUT ON;
DECLARE
lv_num_tab DBMS_SQL.NUMBER_TABLE;
BEGIN
SELECT LEVEL BULK COLLECT INTO lv_num_tab FROM DUAL CONNECT BY LEVEL < 10;
FOR I IN lv_num_tab.FIRST..lv_num_tab.LAST
LOOP
dbms_output.put_line(lv_num_tab(i));
END LOOP;
END;
You may also want to put the whole select in a table. You can use a BULK COLLECT to an array:
CREATE OR REPLACE TYPE t_my_list AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE
PROCEDURE get_tables(p_owner in varchar2)
as
v_res t_my_list;
v_qry varchar2(4000) := '';
begin
v_qry := ' SELECT table_name from all_tables where owner='''||p_owner||'''';
dbms_output.put_line(v_qry);
-- all at once in the table
execute immediate v_qry bulk collect into v_res;
FOR I in 1..v_res.count
loop
dbms_output.put_line(v_res(i));
end loop;
exception
when others then
raise;
end get_tables;
/
begin
get_tables('E') ;
end;
/

Oracle PLSQL Error while executing an anonymous block - Encountered the symbol "LOOP" [duplicate]

Please, explain me how to use cursor for loop in oracle.
If I use next code, all is fine.
for rec in (select id, name from students) loop
-- do anything
end loop;
But if I define variable for this sql statement, it doesn't work.
v_sql := 'select id, name from students';
for rec in v_sql loop
-- do anything
end loop;
Error: PLS-00103
To address issues associated with the second approach in your question you need to use
cursor variable and explicit way of opening a cursor and fetching data. It is not
allowed to use cursor variables in the FOR loop:
declare
l_sql varchar2(123); -- variable that contains a query
l_c sys_refcursor; -- cursor variable(weak cursor).
l_res your_table%rowtype; -- variable containing fetching data
begin
l_sql := 'select * from your_table';
-- Open the cursor and fetching data explicitly
-- in the LOOP.
open l_c for l_sql;
loop
fetch l_c into l_res;
exit when l_c%notfound; -- Exit the loop if there is nothing to fetch.
-- process fetched data
end loop;
close l_c; -- close the cursor
end;
Find out more
try this :
cursor v_sql is
select id, name from students;
for rec in v_sql
loop
-- do anything
end loop;
then no need to open, fetch or close the cursor.
You're not executing that sql string anywhere. Simply do this
v_sql := 'select id, name from students';
open cur for v_sql;
for rec in cur loop
-- do anything
end loop;
Or you can do this
cursor cur is select id, name from students;
open cur;
for rec in cur loop
-- do anything
end loop;
Or you can do this
for rec in (select id, name from students) loop
-- do anything
end loop
You have to use Refcursor if you are making the query at runtime. Actually refcursors are pointers to the query they wont take up any space for the rows fetched.
Normal Cursors will not work for it.
declare
v_sql varchar2(200);
rec sys_refcursor;
BEGIN
v_sql := 'select id, name from students';
open rec for v_sql
loop
fetch
exit when....
-- do anything
end loop;

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 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