Oracle 11g: Using cursors inside a procedure - sql

I'm trying to return two ref cursors from a procedure and having a bit of trouble. What I'm trying to do is grab the info from the first cursor, select a few fields out of it and join to some other info and stick the result into a table variable... then select distinct items from that table into the second cursor. But I can't get this to compile. Can someone tell me what I'm doing wrong please?
type T_CURSOR is REF CURSOR
procedure FetchSL3Details_PRC
(
c_items out T_CURSOR,
c_identifiers out T_CURSOR,
p_niin in char
) as
v_idents IDENTIFIER_TABLE_TYPE:= IDENTIFIER_TABLE_TYPE();
BEGIN
open c_items for
select
its.item_set_id,
its.niin,
its.parent_niin,
its.commodity_id,
its.service_type,
its.sl3_type,
its.qty,
its.created_id,
its.created_dt,
its.modified_id,
its.modified_dt
from
item_set its
start with its.niin = p_niin
connect by prior its.niin = its.parent_niin;
for item in c_items loop
v_idents.extend;
v_idents(v_idents.LAST) := identifier_row_type(item.commodity_id,
get_group_name_fun(item.commodity_id),
0);
v_idents.extend;
v_idents(v_idents.LAST) := identifier_row_type(item.created_id,
get_formatted_name_fun(item.created_id),
0);
v_idents.extend;
v_idents(v_idents.LAST) := identifier_row_type(item.modified_id,
get_formatted_name_fun(item.modified_id),
0);
end loop;
open c_identifiers for
select
distinct(v.id),
v.name,
v.type
from
v_idents v;
END FetchSL3Details_PRC;

You can't use this construct:
for item in c_items loop
with a REF CURSOR. It expects c_items to be a standard PL/SQL CURSOR. That's the immediate cause of the error you're getting. If you want to loop over a REF CURSOR, as far as I know, you need to use explicit FETCH statements and handle the loop condition yourself.
Furthermore, what you say you are trying to do doesn't quite make sense. If you fetch from the c_items cursor within the body of the procedure, returning it to the caller as well is confusing. In your comment, you use the phrase "select into the cursor", which implies that maybe you think of the cursor as a static collection that you can iterate over repeatedly. This is not the case -- a cursor represents an active query in memory. Once a row is fetched from the cursor, it can't be fetched again.
I'm not sure what to suggest exactly since I don't understand the end goal of the code. If you really need to both process the rows from c_items and return it as a usable REF CURSOR, then the only option may be to close and re-open it.

Change this:
open c_identifiers for
select
distinct(v.id),
v.name,
v.type
from
v_idents v;
to:
open c_identifiers for
select
distinct(v.id),
v.name,
v.type
from
TABLE(v_idents) v; -- Use TABLE

Related

Saving result set in variable using a cursor

I have a problem using Oracle SQL to loop over a result set twice.
The problem
I have a cursor that gets me all foreign keys to a given table name. Using the result form this cursor, I loop through all the constraints and disable them. Then I perform a data import and then I need to loop over the same result set and enable them.
CURSOR c_fkeys_inn(tabellnavn IN VARCHAR2)
IS
SELECT table_name,constraint_name, status
FROM user_constraints
WHERE (r_constraint_name,r_owner) IN
(SELECT constraint_name, owner
FROM user_constraints
WHERE owner ='POP'
AND table_name=upper(tabellnavn)
)
AND STATUS = 'ENABLED';
What I would like to do
My brain jumps directly to a variable. I would like to perform the cursor just once, and then save the result from the cursor to a variable.
Is this possible or are there anything I do to save the result from the cursor and loop twice?
Please try this code. I have sightly modified your code to just display the constraint's table names. You can modify the end part of the plsql according to your requirement. Please comment if you have come across any mistakes or issues, thank you.
CREATE or replace PROCEDURE a_proc(name_table varchar)
AS
CURSOR c_fkeys_inn(tabellnavn IN VARCHAR2)
IS
SELECT table_name,constraint_name, status
FROM user_constraints
WHERE STATUS = 'ENABLED'
AND TABLE_NAME=tabellnavn;
names_t c_fkeys_inn%ROWTYPE;
TYPE c_fkeys IS TABLE OF names_t%TYPE;
fkeys c_fkeys;
BEGIN
OPEN c_fkeys_inn(name_table);
FETCH c_fkeys_inn BULK COLLECT INTO fkeys;
CLOSE c_fkeys_inn;
FOR indx IN 1..fkeys.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(fkeys(indx).table_name);
END LOOP;
END a_proc;
To run the code please run a separate plsql block. Please find a simple and a sample plsql block given below.
begin
a_proc('SUPPLIER');
END;

Selecting numbers into array and then looping over them

I need to populate an array with numbers (it is the ID column of the table) and then loop over said array calling a stored procedure.
I am having trouble figuring out how to declare an array in which I won't know the size of until runtime and then populating it.
Currently, I have the following:
declare
type idArray is table of number index by pls_integer;
theIdArray idArray;
begin
select id into theIdArray from table_name_here where report_id = 3449;
end;
This is not working, but I don't know why. I then also need to loop through my array of numbers and call a stored procedure. Something like the following:
for i in theIdArray.FIRST..theIdArray.LAST LOOP
stored_proc_here(i);
END LOOP;
Can someone give me some insight on to how to accomplish this. What I have so far has been derived from examples I sorta-kinda understood.
your code fails because you are using clause into. For filling collections use bulk collect into instead.
declare
type idArray is table of number index by pls_integer;
theIdArray idArray;
begin
select id bulk collect into theIdArray from table_name_here where report_id = 3449;
end;
If all you are doing is looping it could be just as easy to use a cursor loop.
declare
cursor Ids is
select ID from table_name_here where report_id = 3449;
...
for Report in Ids loop
stored_proc_here( Report.ID );
end loop;
You don't have to worry about explicit opening, closing, fetching, allocating or deallocating. All that is handled by the loop.

Calling a function within a loop?

I'm using the following code:
for x in c_body loop
ln_start := decrypt(card_no); --This calls to a function
utl_file.put_line(out_file_card, x.data_line); -- writes data to a file
end loop;
I have card_no declared at the top of my procedure, but I have not given it values.
when I try to use a select statement like this one:
select card_no_from_table
into card_no
from card_table;
It gives me the error that I am trying to insert too many rows into the variable, which I understand. My question therefore, is there a way that I can use the function within the loop?
I thought about having a loop within a loop, or should I try to make a cursor for this?
The reason why I have the function in the loop is, because I need to use the decrypt for every card while it's writing to a file.
Thank you for the help, it's appreciated!
I think it would be better if you use cursor for this.
Try this :
CURSOR csr is SELECT card_no_from_table FROM card_table;
rec csr%rowtype;
BEGIN
OPEN csr;
LOOP
FETCH csr INTO rec;
EXIT WHEN csr%NOTFOUND;
ln_start := decrypt(rec.card_no_from_table);
utl_file.put_line(out_file_card, x.data_line);
END LOOP;
CLOSE csr;
END;
The answer that I accepted did solve my problem, but it was quite expensive time wise:
For future reference I will post what I did and what worked for me in this regard.
Cursor csr is
Select name,
surname,
decrypt(card_no), -- call the decrypt function in the cursor select
to_char(sysdate,'DD/MM/YYYY') data_line
From table, followed by where and joins.
Then I simply executed the function in the loop to write to the file using this loop:
for writeBodyin csr
loop
utl_file.put_line(out_file_card, writeBody.data_line);
end loop;
and it works wonderfully.

Using CURSOR to fetch multiple table names from another table

I've created one table "Meta_Data_Table_Names" where I inserted forty eight table names in the MetaTableName column. And there is another column to provide Row count with corresponding table name.
I wanted to fetch the table name from “Meta_Data_Table_Names” and execute SELECT Query sequentially through Loop for validation purpose.
Whenever, I execute from TOAD , It’s throwing an error:
Table or view does not exist.
Do we need to make a place holder for 'Meta_name' which can be scanned? Or any particular syntax to read the value during Query?
DECLARE
CURSOR c1 IS SELECT MetaTableName FROM Meta_Data_Table_Names;
CURSOR c2 IS SELECT ROW_COUNT FROM Meta_Data_Table_Names;
Meta_name Meta_Data_Table_Names.MetaTableName%TYPE;
Count_num Meta_Data_Table_Names.ROW_COUNT%TYPE;
BEGIN
OPEN c1;
OPEN c2;
FOR i IN 1..48 LOOP
FETCH c1 INTO Meta_name;
FETCH c2 INTO Count_num;
IF (Count_num > 2000)
THEN
SELECT * FROM Meta_Name X
MINUS
SELECT * from ASFNCWK07.Meta_Name#NCDV.US.ORACLE.COM Y
UNION ALL
SELECT * FROM ASFNCWK07.Meta_Name#NCDV.US.ORACLE.COM Y
MINUS
SELECT * FROM Meta_Name X;
ELSE
DBMS_OUTPUT.PUT_LINE ('No Validation is required');
END IF;
END LOOP;
END;
Oracle does not allow you to do queries with dynamic table names, i.e. if the table name is not known at compile time. Do do that, you need Dynamic SQL, which is a bit too broad to go into here.
There are a number of problems with your code.
Firstly, we cannot use variable names in normal SQL: for this we need dynamic SQL. For instance:
execute immediate 'select 1 from '|| Meta_Name || into n;
There are a lot of subtleties when working with dynamic SQL: the PL/SQL documentation devotes a whole chapter to it. Find out more.
Secondly, when executing SQL in PL/SQL, we need need to provide a target variable. This must match the projection of the executed query. When selecting a whole row the %ROWTYPE keyword is useful. Again the documentation covers this: find out more. Which leads to ...
Thirdly, because you're working with dynamic SQL and you don't know in advance which tables will be in scope, you can't easily declare target variables. This means you'll need to use ref cursors and/or Type 4 dynamic SQL techniques. Yikes! Read Adrian Billington's excellent blog article here.
Lastly (I think), the UNION ALL in your query doesn't allow you to identify which rows are missing from where. Perhaps that doesn't matter.

Viewing query results with a parameters in Oracle

I need to run big queries (that was a part of SP) and look at their results (just trying to find a bug in a big SP with many unions. I want to break it into parts and run them separately).
How can I do that if this SP have few parameters? I don't want to replace them in code, it would be great just to add declare in a header with a hardcode for this parameter.
I've tried something like this:
DECLARE
p_asOfDate DATE := '22-Feb-2011';
BEGIN
SELECT * from myTable where dateInTable < p_asOfDate;
END
But it says that I should use INTO keyword. How can I view this results in my IDE? (I'm using Aqua data studio)
I need to do that very often, so will be very happy if will find a simple solution
You are using an anonymous block of pl/sql code.
In pl/sql procedures you need to specify a target variable for the result.
So you first need to define a variable to hold the result in the declare section
and then insert the result data into it.
DECLARE
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
select * into p_result from myTable where dateInTable < p_asOfDate;
END
That said you will probaply get more than one row returned, so I would use
a cursor to get the rows separately.
DECLARE
CURSOR c_cursor (asOfDate IN DATE) is
select * from myTable where dateInTable < asOfDate;
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
OPEN c_cursor(p_asOfDate);
loop
FETCH c_cursor into p_result;
exit when c_cursor%NOTFOUND;
/* do something with the result row here */
end loop;
CLOSE c_cursor;
END
To output the results you can use something like this for example:
dbms_output.put_line('some text' || p_result.someColumn);
Alternatively you can execute the query on an sql command-line (like sqlplus)
and get the result as a table immediately.
I hope I understood your question correctly...
update
Here is a different way to inject your test data:
Use your tools sql execution environemnt to submit your sql statement directly without a pl/sql block.
Use a "&" in front of the variable part to trigger a prompt for the variable.
select * from myTable where dateInTable < &p_asOfDate;
The Result should be displayed in a formatted way by your tool this way.
I do not know about Aqua, but some tools have functions to define those parameters outside the sql code.