How do I define another temporary variable and fetch into it? - oracle

Here is My code;-
CREATE OR REPLACE PROCEDURE GetDeails
(c_name VARCHAR2,
calories NUMBER)
DECLARE
CURSOR cur IS SELECT CATEGORY.Name FROM CATEGORY INNER JOIN FILLING ON CATEGORY.CategoryID = FILLING.CategoryID
WHERE c_name=FillING.Name AND calories=GramCalories;
fil cur%ROWTYPE;
BEGIN
OPEN cur;
LOOP
FETCH cur INTO fil;
EXIT WHEN (cur%NOTFOUND);
IF fil%NOTFOUND THEN
DBMS_OUTPUT.PUTLINE('NotFound');
ELSE
DBMS_OUTPUT.PUTLINE(fil.c_name, fil.calories);
END IF;
END LOOP;
CLOSE cur;
END GetDetails;
/

Basically your PROCEDURE statement is good, but having some little issues, such as :
Convert the name GetDeails to GetDetails in order to have the
matching name with the one given at the end after the last END
keyword. Indeed, using the PROCEDURE's name twice is redundant, so,
not needed.
There should be IS or AS keyword just after IN parameters' list, and the keyword DECLARE should be removed.
DBMS_OUTPUT.PUTLINE should be converted to DBMS_OUTPUT.PUT_LINE,
and two matching columns( Name and GramCalories ) should be provided in the CURSOR's SELECT list.
cursor attribute may not be applied to non-cursor FIL but to CUR
SQL> SET serveroutput ON
SQL> CREATE OR REPLACE PROCEDURE GetDetails( c_name VARCHAR2, calories NUMBER ) IS
CURSOR cur IS
SELECT f.Name, c.GramCalories
FROM CATEGORY c
JOIN FILLING f
ON f.CategoryID = c.CategoryID
WHERE c_name=f.Name
AND calories=GramCalories;
fil cur%ROWTYPE;
BEGIN
OPEN cur;
LOOP
FETCH cur INTO fil;
EXIT WHEN (cur%NOTFOUND);
IF cur%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('NotFound');
ELSE
DBMS_OUTPUT.PUT_LINE(fil.name, fil.calories);
END IF;
END LOOP;
CLOSE cur;
END;
/

The answer by #Barbaros solves most of your issue but can be further refined.
The IF statement within the loop is completely unnecessary as it will never return True when executed. If it were true the exit
statement preceding it would have exited the loop; thus no message. This is redundant; making a test where the result is already known. You could reverse the order and put the exit after the IF...END IF. But then the 'not found' message would always be produced. You can use cur%rowcount after the loop to generate the message correctly.
The dbms_output_put_line(fil.name,fil.calories) has 2 errors.
variable fil.calories does not exist. GramCalories was not selected in your original (as pointed out) nor aliased in the revision. So is not part of the cursor and thus not part of the
cursor row type.
It requires a single string parameter, as is there are 2 parameters.
Taking these into account we get:
create or replace procedure getdetails( c_name varchar2, calories number ) is
cursor cur is
select f.name, c.gramcalories
from category c
join filling f
on f.categoryid = c.categoryid
where c_name=f.name
and calories=gramcalories;
fil cur%rowtype;
begin
open cur;
loop
fetch cur into fil;
exit when (cur%notfound);
dbms_output.put_line(fil.name || ' ' || fil..gramcalories);
end loop;
if cur%rowcount = 0 then
dbms_output.put_line('Not Found');
end if;
close cur;
end getdetails;
/
As a matter of style:
Avoid the CamelCase naming convention. Oracle always folds object names to uppercase. Thus it just makes Oracle generated references difficult to read. Instead use words separated by Underscore (_).
Unlike Barbaros I do not consider using the procedure (function,
package, ...) name on the terminating end as redundant but more as a
closure as much as an 'end if'. Yes it is syntactically optional, but
optional is not the same a redundant. I always exercise that option.
So developed your style (subject to institutional/customer mandated
standards), but be consistent with it.

Related

ORA-06511: PL/SQL: cursor already open. I am closing my cursor but no luck

I am getting the ORA-06511: PL/SQL: cursor already open ERROR.
Not sure why I am getting this error since I am closing my cursor.
Please see code below.
BEGIN
OPEN findem; ---OPENING HERE!
FOR crfindem IN findem LOOP
FETCH findem into other1, other2, other3;
l_CollectionOfRows(Counter).tmps_key := other1;
l_CollectionOfRows(Counter).tmps_cfb_rate := other2;
l_CollectionOfRows(Counter).tmps_engagement_pay_rate := other3;
Counter := Counter + 1;
END LOOP;
CLOSE findem;---CLOSING HERE!
FORALL i IN l_CollectionOfRows.FIRST .. l_CollectionOfRows.LAST
UPDATE Base.Table
SET MARGIN = :PAGE56_MARGIN,
PERCENT = :PAGE56_MARGIN + l_CollectionOfRows(i).rate,
PAY_RATE = (l_CollectionOfRows(i).pay_rate * (:PAGE56_MARGIN + l_CollectionOfRows(i).rate)) + l_CollectionOfRows(i).pay_rate
WHERE tmps_key = l_CollectionOfRows(i).tmps_key;
END;
I read from some online threads that for every Insert/Update statement, Oracle will create an implicit cursor. If this is the case how do you treat those implicit cursors that Oracle creates?
You are getting that error because you are opening the same cursor twice: the FOR construct already does all these things for you:
FOR opens the cursor
FOR implicitly declares a record variable (the one named crfindem, in your code) that will receive the values for each row read from the cursor
FOR loops on every row and assigns the values of the current row to the crfindem variable
FOR automatically closes the cursor at the end of the loop
so you don't need any OPEN/CLOSE/FETCH .. INTO commands if you are using a FOR loop:
see this simple example: itjust works.
declare
cursor cur is select * from user_tab_comments;
begin
for c in cur loop
DBMS_OUTPUT.PUT_LINE( c.table_name || ' - ' || c.comments);
end loop;
end;
but if i try to open the cursor before using the for loop, I will get your same error because the cursor is already open and the for construct is trying to open it again:
declare
cursor cur is select * from user_tab_comments;
begin
open cur; -- this is not needed and will cause problems
for c in cur loop --! ERROR: here I am trying to open AGAIN the same cursor
DBMS_OUTPUT.PUT_LINE( c.table_name || ' - ' || c.comments);
end loop;
end;
so, you either must choose if you want to write this code:
declare
cursor cur is select * from user_tab_comments;
begin
for c in cur loop
DBMS_OUTPUT.PUT_LINE( c.table_name || ' - ' || c.comments);
end loop;
end;
or avoid using the FOR construct and do all the open/fetch/close operations by yourself, by writing this:
declare
-- I cant' use "select *" here:
-- if I use "fetch into" to a precise list of variables,
-- I have to extract exactly the fields I want to assign:
cursor cur is
select table_name,comments
from user_tab_comments;
tabname varchar(100);
tabcomment varchar2(4000);
begin
open cur;
loop
fetch cur into tabname,tabcomment;
exit when cur%notfound;
DBMS_OUTPUT.PUT_LINE( tabname || ' - ' || tabcomment);
end loop;
close cur;
end;
Your error is that your code is trying to do both these things at the same time.
you should have written this:
-- OPEN findem; NO NEED TO OPEN THE CURSOR (when using FOR)
FOR crfindem IN findem LOOP
--- FETCH findem into other1, other2, other3; FOR ALREADY DOES THIS: the values are in crfindem
l_CollectionOfRows(Counter).tmps_key := crfindem.name_of_the_first_field;
l_CollectionOfRows(Counter).tmps_cfb_rate := crfindem.name_of_the_second_field;
l_CollectionOfRows(Counter).tmps_engagement_pay_rate := crfindem.name_of_the_third_field;
Counter := Counter + 1;
END LOOP;
--- CLOSE findem; NO NEED TO CLOSE THE CURSOR (when using FOR)
Now let me add some considerations about your code (and about this example):
I don't see where you initialize your Counter variable: you MUST initialize it to 0 before entering the loop, because otherwise its initial value will be NULL and will stay null for the whole operation because (NULL + 1) evaluates again to NULL.
I don't see how your cursor is declared, so I don't know the names of the fields it extracts. in the code above I used the "fake" names name_of_the_first_field, name_of_the_second_field, name_of_the_third_field... but you must use the correct field names returned by your query
if your cursor returns some calculated value (like "select 1+2, sysdate, null from dual") you must assign a name to the calculated column to make it accessible by giving an alias to each calculated column you extract ("select 1+2 AS first_name, sysdate AS second_name, null as third_name from dual")
Edit... another info: you don't really need to declare a variable for each field even when you are explicitly using open/fetch/close: you can declare a RECORD variable (that will contain all column values with the same column names, exactly like the for loop does) by using the %ROWTYPE syntax. my example becomes like this, using %rowtype:
declare
cursor cur is select * from user_tab_comments;
-- here I am declaring a variable named c that is a RECORD variable:
-- it can contain a whole row returned by cursor cur
c cur%rowtype;
begin
open cur;
loop
fetch cur into c;
exit when cur%notfound;
DBMS_OUTPUT.PUT_LINE( c.table_name || ' - ' || c.comments);
end loop;
close cur;
end;

Input table and column name in Oracle SQL

I want to complete a task that says:
Search in the user given table, in the user given column the user given value using explicite cursor. This means I have to use the & operator to input the table name, column name and value. If I found the given object, give a varchar found else not found. How to input the table name, column name and the value (that is of correct type of course)?
I tried it but failed miserably:
declare
#Mytable varchar2;
#Mycolumn varchar2;
Myvalue #Mycolumn%TYPE;
oneLine #Mytable%ROWTYPE;
found varchar2;
cursor kurzor is select * from #Mytable;
begin
open kurzor;
loop
fetch kurzor into oneLine;
if oneLine.#Mycolumn = Myvalue then
found='found';
end if;
exit when kurzor%NOTFOUND;
end loop;
close kurzor;
end;
/
You don't need to describe variables for table name, column name and etc. in section DECLARE. Just use substitution variables.
DECLARE
CURSOR cur IS SELECT &myColumn FROM &myTable;
currentVal VARCHAR2(4000);
isFound VARCHAR2(10):= 'not found';
BEGIN
OPEN cur;
LOOP
FETCH cur INTO currentVal;
EXIT WHEN cur%NOTFOUND;
IF currentVal = &myValue THEN
isFound:= 'found';
EXIT;
END IF;
END LOOP;
CLOSE cur;
dbms_output.put_line(isFound);
END;

Line break in dbms_output.put_line

I run some Oracle procedure with the help of cursor and get output in logfile via dbms_output.put_line.
What I would like to do is break line on server_name, is it possible with dbms_output.put_line?
currently it list everything together which doesn't look tidy.
Also some server id appears as 1234.9 or 1234.88, is it possible to set numformat like 999.99? somehow I can't do col server_id for 999.99 within procedure.
create procedure proc (vServer IN VARCHAR2, vServerID IN NUMBER)
IS
CURSOR curTable
IS
SELECT server_name, server_id
FROM tab1
WHERE server_name = vServer
and server_id = vServerID;
BEGIN
FOR rec1 IN curTable
LOOP
dbms_output.put_line(rec1.server_name || ' '|| rec1.server_id);
END LOOP;
END proc;
Sample required output:
S1 1234
S1 1234
S1 1234
S2 5678
S2 5678
Doing a break on server_id only makes sense if you specify that column as the sort order. Then, you have to code the break logic yourself. Also, you can use TO_CHAR to format the number as you like.
Here is the code that should do what you want:
create procedure proc (vServer IN VARCHAR2, vServerID IN NUMBER)
IS
CURSOR curTable
IS
SELECT server_name, server_id
FROM tab1
WHERE server_name = vServer
and server_id = vServerID
ORDER BY server_id ;
l_last_server_id tab1.server_id%TYPE := 0;
BEGIN
FOR rec1 IN curTable
LOOP
-- Test for break:
IF last_server_id != rec1.server_id THEN
-- Break detected:
dbms_output.put_line('---'); -- DBMS_OUTPUT will not print a blank line.
END IF ;
dbms_output.put_line(rec1.server_name || ' '|| TO_CHAR(rec1.server_id,'999.99');
l_last_server_id := rec1.server_id ;
END LOOP;
END proc;
This seems like something you'd do with plain SQL, but assuming this is a PL/SQL exercise, you can use a variable to track the last value seen and add an extra line if it changes:
...
last_server_name tab1.server_name%type;
BEGIN
FOR rec1 IN curTable
LOOP
if last_server_name is not null
and rec1.server_name != last_server_name then
dbms_output.new_line;
end_if;
dbms_output.put_line(rec1.server_name
|| ' '|| to_char(rec1.server_id, '99990.00'));
last_server_name := rec1.server_name;
END LOOP;
END proc;
You can use to_char() inside the dbms_output call, with whatever format model is suitable - you're doing an implit conversion anyway; or in the cursor if you don't ned the ID as a number within the loop.
Remember that you'll only see the output if your client is set up for it - with set serveroutput on, for example. You can't generally rely on that, so it's not a good idea to use dbms_output in real code for anything except debugging.
And as Scott K. pointed out, you need to order your cursor results for this to work, so add order by server_name to the cursor query. Except, you're only looking for a single server name and ID anyway as you're filtering on vServer and vServerID, so your original query can't produce your original output anyway...

Need an alternative solution to this oracle query?Without using (flag) variable

Question:
Create a Doctor table (Docname, Qualification, Specialization, Working_shift).
Use parameterized cursor to check the availability of doctors given the specialization
and working shift of the day to serve the patients
I am just learning databases so if the question may seem trivial i apologize for that.
Getting the desired output on inputting the values but i need an alternative way to solve the question without using flag variable (so that i could get the exception)...if i don't use the flag it prints the exception as well as the docname and qualification
I am using oracle(cursor in a normal pl/sql block) to execute this query.
Solution:
--table creation
create table doctor
(
docname varchar2(20),
qualification varchar2(20),
specialization varchar2(20),
shift varchar2(20)
)
my solution
declare
cursor c1 (specialization varchar2,shift varchar2) is select docname,qualification from doctor
where specialization='&sp' and shift='&shift'
sp doctor.specialization%type;
shift doctor.shift%type;
flag number(10);
begin
flag:=0;
for r1 in c1(sp,shift)
loop
if c1%found then
flag:=1;
dbms_output.put_line('Doctor is available');
dbms_output.put_line('Docname: '||r1.docname);
dbms_output.put_line('qualification: '||r1.qualification);
else
flag:=0;
end if;
end loop;
if flag=0 then
dbms_output.put_line('Invalid specialization/shift');
end if;
end;
Try given below code
declare
cursor c1 (specialization varchar2,shift varchar2)
is
select docname,qualification
from doctor
where specialization='&sp'
and shift='&shift'
sp doctor.specialization%type;
shift doctor.shift%type;
flag number(10);
begin
flag:=0;
for r1 in c1(sp,shift)
loop
if c1%found then
flag:=1;
dbms_output.put_line('Doctor is available');
dbms_output.put_line('Docname: '||r1.docname);
dbms_output.put_line('qualification: '||r1.qualification);
else
raise;
end if;
end loop;
exception
when others then
dbms_output.put_line('Invalid specialization/shift');
end;
You don't need to reset the flag within the loop, you already initialised it to 0 at the start of the procedure.
You dont need to check c1%found because you're inside the loop; by definition a record was found, otherwise it wouldn't go into your loop code.
Your cursor should use the variables provided, not the SQL*Plus substitution variables, e.g.:
cursor c1 (specialization varchar2,shift varchar2) is
select docname,qualification
from doctor
where doctor.specialization=c1.specialization
and doctor.shift=c1.shift;
If you don't want to have to use all those aliases, you can use a naming convention to distinguish between the different identifiers (shift vs shift), e.g.:
cursor c1 (i_specialization varchar2, i_shift varchar2) is
select docname,qualification
from doctor
where specialization=i_specialization
and shift=i_shift;
Note also, you missed a semicolon at the end of the query.
Finally:
If you change your loop as follows, it should work fine:
for r1 in c1(&sp,&shift)
loop
flag:=1;
dbms_output.put_line('Doctor is available');
dbms_output.put_line('Docname: '||r1.docname);
dbms_output.put_line('qualification: '||r1.qualification);
end loop;
Now, your last bit of code:
if flag=0 then
dbms_output.put_line('Invalid specialization/shift');
end if;
will work fine - it will only execute if flag is still 0 (i.e. the query found no rows).
If you do not use parameters in "c1" cursor you do not need it...
DECLARE
CURSOR c1 IS
SELECT docname, qualification
FROM doctor
WHERE specialization = '&sp'
AND shift = '&shift';
TYPE c1_ntt IS TABLE OF c1%ROWTYPE;
l_c1 c1_ntt;
BEGIN
OPEN c1;
FETCH c1 BULK COLLECT INTO l_c1;
CLOSE c1;
IF l_c1.COUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20000, 'Invalid specialization/shift');
END IF;
FOR indx IN l_c1.FIRST..l_c1.LAST LOOP
DBMS_OUTPUT.PUT_LINE('Doctor is available');
DBMS_OUTPUT.PUT_LINE('Docname: ' || l_c1(indx).docname);
DBMS_OUTPUT.PUT_LINE('qualification: ' || l_c1(indx).qualification);
END LOOP;
END;

pl/sql loop records select oracle plsql

I have a select statement that I am trying to loop over and increment a variable based on the condition of the select statement, then return the variable as an out so I can do something with it in some front end code. I am using oracle 11g and I am seeing a few ways I can do this... but I am not sure which is the best way. I have some of what I am trying to do below, but again stopped because of confusion.
First I am setting my proc and 'in variable'
PROCEDURE SEEKER (pMonkeyID IN Number, vMarkCounter OUT Number)
AS
BEGIN
CURSOR seeker_cur IS
Select Mokney_approved, Monkey_vaulted
from MonkeyBookApps
where MonkeyID = pMonkeyID
and Monkey_doc_type = 'BANANA'
order by docu_approv_timestamp,monkey_doc_type,monkey_doc_approved desc
OPEN seeker_cur;
begin
OPEN Seeker_cur;
vMarkCounter := 0;
Here is the part I am not sure about. Should I loop and then exit if the condition is not met or should I do an if statement and somehow determine if there is a record that could be greater than one? If so how would that work? Is there a benefit to doing one way over the other? So... I am going to sudo-code what I am trying to do (below):
FOR (however many records) in Seeker_cur
IF seeker_cur (not found) or (returns no records)
EXIT or (break for loop);
ELSE
LOOP
vMarkCounter := vMarkCounter + 1;
EXIT WHEN seeker_cur is out of records (somehow)
END IF;
END LOOP;
END;
END SEEKER;
I am sure there are a few ways to do this. What ways would you suggest?
why dont you use implicit cursor , it will open and close itself:
DECLARE
CURSOR seeker_cur IS
Select Mokney_approved, Monkey_vaulted
from MonkeyBookApps
where MonkeyID = pMonkeyID
and Monkey_doc_type = 'BANANA'
order by docu_approv_timestamp,monkey_doc_type,monkey_doc_approved desc;
vMarkCounter number:=0;
BEGIN
FOR i IN seeker_cur
LOOP
vMarkCounter := vMarkCounter+1;
END LOOP;
dbms_output.put_line(vMarkCounter);
END;
It seems to me that the solution your problem might be as simple as this:
SELECT COUNT(*)
INTO l_some_local_variable
FROM monkey_book_apps
WHERE monkey_id = p_monkey_id
AND monkey_doc_type = 'BANANA';
RETURN l_some_local_variable;
Avoiding PL/SQL loops and using the simplest SQL possible is (almost always) the most efficient way. Tom Kyte calls the row-by-row execution of LOOPs "slow-by-slow".

Resources