Loop through plsql code for a number of tables - oracle

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;

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 ;

Oracle xmlexists in pl/sql block

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?

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;

PLSQL dynamic query

I have a table A which has column A which holds table names as values.
All these tables have a common column C. I need maximum value of this column for each table.
I tried this using dynamic SQL but I'm getting errors. Please suggest.
DECLARE
query1 VARCHAR2(100);
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
Cursor cursor_a IS
SELECT a FROM A;
BEGIN
Open cursor_a;
LOOP
Fetch cursor_a INTO c_table2;
EXIT WHEN cursor_a%notfound;
query1 := 'SELECT max(object_ref) AS "c_obj" FROM c_table' ;
EXECUTE IMMEDIATE query1;
dbms_output.put_line('Maximum value: '|| c_table || c_obj);
END LOOP;
Close cursor_a;
END;
Dynamic SQL can't see your PL/SQL variable: you need to pass it a string which can be executed in the scope of the SQL engine. So you need to concatenate the table name with the statement's boilerplate text:
query1 := 'SELECT max(c) FROM ' || variable_name;
You also need to return the result of the query into a variable.
Here is how it works (I've stripped out some of the unnecessary code from your example):
DECLARE
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
BEGIN
for lrec in ( select a as tab_name from A )
LOOP
EXECUTE IMMEDIATE 'SELECT max(object_ref) FROM ' || lrec.tab_name
into c_obj ;
dbms_output.put_line('Maximum value: '|| lrec.tab_name
|| '='|| c_obj);
END LOOP;
END;
There is some miss match in veriables that you had used i.e.
declared as "c_table" but accessing as "c_table2"
Each table common column name is "C" but accessing as "object_ref"
In dynamic query use INTO keyword to store the value to your varibale
Suggestions
Use concat() function to prepare the query dynamically i.e. something like:
SET #SQL := CONCAT('SELECT max(c) INTO ', c_obj, ' FROM ',c_table);
Steps of implementing dynamic query is:
SET #SQL = <your dynamic query>
PREPARE stmt FROM #SQL;
EXECUTE stmt;
Sample code:
DECLARE
query1 VARCHAR2(100);
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
CURSOR cursor_a IS
SELECT a FROM A;
BEGIN
OPEN cursor_a;
LOOP
FETCH cursor_a INTO c_table;
EXIT WHEN cursor_a%notfound;
SET #SQL := CONCAT('SELECT max(object_ref) AS c_obj INTO ', c_obj, ' FROM ',c_table);
PREPARE stmt FROM #SQL;
EXECUTE stmt;
dbms_output.put_line('Maximum value: '|| c_table || c_obj);
END LOOP;
CLOSE cursor_a;
END;

For loop with Table name in Stored Procedures

I am working on Oracle stored procedures.
My requirement is below
IF variable1 := 'true"
THEN
tableName=abr
ELSE
tableName=mvr
END IF;
FOR i IN (select unique(row1) as sc from tableName t where t.row2 = 'name') LOOP
BEGIN
-- required Logic
END
END LOOP;
But here I am not able to pass the table name in tableName parameter. How to do it?
You'll need to use Execute Immediate - it's designed for operations that aren't known until run time.
For normal operations, Oracle must know the tables and columns at compile time. You can't do SELECT * FROM tableName because it has no idea what tableName is and therefore it can't be compiled correctly.
Instead, you can do EXECUTE IMMEDIATE 'SELECT * FROM ' || tableName;
You can select your results INTO a variable, loop the result set, or BULK COLLECT into a structure and then iterate that.
For a simple select into, you can do this:
EXECUTE IMMEDIATE 'SELECT COL1, COL2 FROM ' || tableName INTO V_COL1, V_COL2
V_COL1 & V_COL2 are just local variables, tableName is a string representing your table name, and COL2 and COL2 are columns in the table you're selecting from. You can use the likes of ALL_TAB_COLUMNS to get the structure of a table dynamically.
Here is an example from Oracle docs:
CREATE OR REPLACE PROCEDURE query_invoice(
month VARCHAR2,
year VARCHAR2) IS
TYPE cur_typ IS REF CURSOR;
c cur_typ;
query_str VARCHAR2(200);
inv_num NUMBER;
inv_cust VARCHAR2(20);
inv_amt NUMBER;
BEGIN
query_str := 'SELECT num, cust, amt FROM inv_' || month ||'_'|| year
|| ' WHERE invnum = :id';
OPEN c FOR query_str USING inv_num;
LOOP
FETCH c INTO inv_num, inv_cust, inv_amt;
EXIT WHEN c%NOTFOUND;
-- process row here
END LOOP;
CLOSE c;
END;
/
http://docs.oracle.com/cd/B12037_01/appdev.101/b10795/adfns_dy.htm
You are going to have to build a for loop for each table then use your logic to determine which loop you will execute.

Resources