Calling a function within a loop? - sql

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.

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;

performance anaysis (select in loop vs function call in loop )

i will like to ask a performance related question here my question is which approach is best
1 adding subquery in loop
declare
test varchar2(50);
FOR Lcntr IN 1..20
LOOP
update emp set empno='50' where empname=test;
END LOOP;
2 adding function call in loop or making the function of the above query and calling it in loop
declare
test varchar2(50);
FOR Lcntr IN 1..20
LOOP
temp:=update('argument');
END LOOP;
If your function update just call the same sql update, it dosen't matter are you call it directly of from stored function.
In common the best way is use one sql statement (update or merge) for update whole dataset what you need.
But you update are look like strange:
In first pl/sql block you declare variable test. And test is equal null. And after that you try update table by comparing with null - no any rows will be affected.
In second pl\sql block you declare variable test too, but use varable temp. It will raise error in compilation.

Oracle 11g: Using cursors inside a procedure

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

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.

How to get rid of "Error 1329: No data - zero rows fetched, selected, or processed"

I have a stored procedure which does not need to return any values. It runs smoothly and without any problem. However, it outputs an error message after finishing its run:
Error: No data - zero rows fetched, selected, or processed
How can I get rid of this error message?
CREATE PROCEDURE `testing_proc`()
READS SQL DATA
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE l_name VARCHAR(20);
DECLARE my_cur CURSOR FOR
SELECT name FROM customer_tbl;
OPEN my_cur;
my_cur_loop:
LOOP FETCH my_cur INTO l_name;
IF done = 1 THEN
LEAVE my_cur_loop;
END IF;
INSERT INTO names_tbl VALUES(l_name);
END LOOP my_cur_loop;
CLOSE my_cur;
END
I guess you just forgot to include the following line in your post:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
Your code is correct, but bug/strange behaviour of mysql causes the warning to appear even if it was handled. You can avoid that if you add a "dummy" statement to the end of your procedure that invovles a table and is successful, this will clear the warning. (See http://dev.mysql.com/doc/refman/5.5/en/show-warnings.html)
In your case:
SELECT name INTO l_name FROM customer_tbl LIMIT 1;
after the end of the loop.
On MySQL 5.5.13 the warning disappears, on Linux and Windows.
I commented on MySQL Bug 60840 and I hope they will fix it some time in the future...
You need to define a continue handler like:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
So it would look like:
DECLARE done INT DEFAULT 0;
DECLARE l_name VARCHAR(20);
DECLARE my_cur CURSOR FOR
SELECT name FROM customer_tbl;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN my_cur;
my_cur_loop:
LOOP FETCH my_cur INTO l_name;
IF done = 1 THEN
LEAVE my_cur_loop;
END IF;
INSERT INTO names_tbl VALUES(l_name);
END LOOP my_cur_loop;
CLOSE my_cur;
I ran into this and pulled out my hair till I ran across this in the official mysql docs
Before MySQL 5.6.3, if a statement that generates a warning or error
causes a condition handler to be invoked, the handler may not clear
the diagnostic area. This might lead to the appearance that the
handler was not invoked. The following discussion demonstrates the
issue and provides a workaround.
Click the link and scroll to the bottom for details but the fix was to include a successful select INSIDE the CONTINUE HANDLER:
DECLARE CONTINUE HANDLER FOR NOT FOUND
BEGIN
SELECT 1 INTO #handler_invoked FROM (SELECT 1) AS t;
END;
I tried the solutions in here and none, including the continue handler worked for me. I still get the messages in the MySQL error log. I discovered this also with my "select ... into ..." which made sense, but I really thought the continue handler would work for the cursors. Either way I found using "found_rows()" to find out if any rows were returned worked perfectly. This mean that the simple "select into" statements have to be converted to cursors, but it isn't much work and does solve the problem.
DECLARE v_rowcount integer unsigned;
DECLARE cur_entries cursor for
select app_name, proc_name, error_code, sum(occurrences) occurrences
from that_table...;
open cur_entries;
set v_rowcount = found_rows();
if v_rowcount > 0 then
fetch cur_entries into v_app_name, v_proc_name, v_error_code, v_occurrences;
...
end if;
close cur_entries;
I wrote this up on my personal blog here: http://tinky2jed.wordpress.com/technical-stuff/mysql/mysql-no-data-zero-rows-fetched-how-to-code-for-it/
Normally this happens when you overshoot a cursor range, so checkout the loop conditions where the FETCH statement is
I don't know if this fixes the cursor issue, but I ran into this warning with a stored function and found that if you use:
RETURN (SELECT x From myTable...);
instead of
SELECT x into myVar...return myVar
I got this from this helpful doc:
http://bugs.mysql.com/bug.php?id=42834
I was getting the same error in my code, and I realized that I had not incremented my loop variable (using while loop) and hence, the loop was going infinite.
In your code too, you are not setting "done" to 1 anywhere, and I think the code is showing error because of that.
In the below code, instead of the variable "done", I have added a variable "count" that is initialized with the number of records in the table and is decremented after each insertion. The loop is terminated when count=0:
CREATE PROCEDURE `testing_proc`()
READS SQL DATA
BEGIN
DECLARE count INT;
DECLARE l_name VARCHAR(20);
SELECT count(*) into count from customer_tbl;
DECLARE my_cur CURSOR FOR
SELECT name FROM customer_tbl;
OPEN my_cur;
my_cur_loop:
LOOP FETCH my_cur INTO l_name;
INSERT INTO names_tbl VALUES(l_name);
SET count = count - 1;
IF count = 0 THEN
LEAVE my_cur_loop;
END IF;
END LOOP my_cur_loop;
CLOSE my_cur;
END
I hope this helps!