Input table and column name in Oracle SQL - oracle

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;

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;

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

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.

function not returning a value for no rows returned

I created a function that takes a movie id as input and returns stock information based from the ID. The function mostly works but if I want to retrieve information from a movie that is not in the database(returns no rows) nothing returns. Can't figure out why?
doesn't give me an error when i call an ID that returns no rows so exception handling wouldn't work.
create or replace function stock_info
(p_id IN NUMBER
)
return VARCHAR2
IS
cursor c1 is
select movie_id, movie_title, movie_qty
from mm_movie
where p_id = movie_id;
lv_movie_info VARCHAR2(100);
BEGIN
for i in c1 loop
if p_id = i.movie_id then
lv_movie_info := i.movie_title || ' is available: ' || i.movie_qty || ' on the shelf';
else
lv_movie_info := 'no data found';
end if;
end loop;
return lv_movie_info;
END STOCK_INFO;
/
The reason you don't get anything when there is no data is that the loop doesn't execute. Logically the For expression says "execute the following loop for every row returned in the cursor" but there are no rows in the cursor so it never executes the loop. Further the structure actually indicates you are expecting multiple for a given p_id. If that's not the case you can eliminate the cursor all together. Assuming p_id is the primary key you have either 0 or 1 row so:
create or replace function stock_info (p_id in number)
return text
is
lv_movie_info varchar2(100);
begin
select i.movie_title || ' is available: ' || i.movie_qty || ' on the shelf'
into lv_movie_info
from mm_movie i
where p_id = movie_id;
return lv_movie_info;
exceptions
when no_data_found
then return 'no data found';
end stock_info;
Of course if do expect more that 1 row the cursor is needed, but the IF is not as the were clause guarantees it's true. Still with 0 rows the loop will not be executed so the 'no data found' message needs to go after "End Loop".
Belayer
the cursor statement you used fetches data from the in parameter. i.e., in the cursor select you limiting based on the movie id passed.
on passing a movie id which is not in the data base, the cursor select statement would not fetch any records, and so the flow won't even go inside the for loop.
if you wanted to return no data found - on passing a movie id which is not in the database, two ways to resolve
1. before the loop, have select statement to set a flag to Y or N if exists according and to have your requirement.
2. in if not using for cursor, there is an option to check not found...
sample:
declare
cursor c1 is select * from table_sample; -- empty table
c_rec c1%rowtype;
begin
open c1;
fetch c1 into c_rec;
if c1%notfound then
dbms_output.put_line('not found');
end if;
close c1;
end;
-- output
not found

String Comparision in If condition in PL/SQL

I have the following code
create or replace procedure deact_user (i_email in varchar2)
as
var1 varchar2(200);
begin
for em_id in (select abc.emai_id from abc)
loop
if (i_email <> em_id) then
dbms_output.put_line('Not working');
else
dbms_output.put_line('Working');
end if;
end loop;
end;
I need to compare the i_email which is a input parameter with em_id which is a for loop which loops the table abc having field as emai_id.
Iam facing error PLS=00306 wrong type of arguments in call to '!='
Please help
When you use a for loop with select, its creates a record type. To access the value, you have to change your if to this:
if (I_email <> em_id.emai_id)
.....
That should solve your problem. Now, on the other hand, it would be quicker (and easier) to just query with a where clause using your variable. (that way, you wouldn't need a for loop).
I recommend you to use Cursors for read content and compare values like this:
create or replace procedure deact_user (i_email in varchar2)
as
var_id varchar2(200);
cursor cur_em_id is select abc.emai_id from abc;
begin
open cur_em_id;
loop
fetch cur_em_id into var_id;
exit when cur_em_id%NOTFOUND;
if (i_email <> var_id) then
dbms_output.put_line('Not working');
else
dbms_output.put_line('Working');
end if;
end loop;
close cur_em_id;
end;

Oracle: How to populate/insert row to a Ref Cursor?

Really need help regarding Ref Cursor. I have a Stored Procedure GET_PERSONROLES that have parameter type ref cursor. I just wanted to pupulate this ref cursor manually like inserting a row to the refcursor.
Can I insert a row into a refcursor though a loop?
Thank you in advance.
The procedure depends on this publicly declared type:
create or replace package types
as
type cursorTypePersonRole is ref cursor;
end;
Here is my pseudo-codeL
create or replace PROCEDURE GET_PERSONROLES
(
P_CURSOR IN OUT types.cursorTypePersonRole
) AS
REFCUR SYS_REFCURSOR;
TYPE REFTABLETYPE IS RECORD (
IS_MANAGER_LEVEL1 VARCHAR2(1),
IS_MANAGER_LEVEL2 VARCHAR2(1)
);
TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
PERSONROLES_TABLETYPE TABLETYPE;
BEGIN
--calls another stored proc to populate REFCUR with data without problem
MY_STOREDPROC('12345', REFCUR);
LOOP
FETCH REFCUR BULK COLLECT INTO PERSONROLES_TABLETYPE;
EXIT WHEN PERSONROLES_TABLETYPE.COUNT = 0;
FOR indx IN 1 .. PERSONROLES_TABLETYPE.COUNT
LOOP
-- I'm able to query perfectly the values of IS_MANAGER_LEVEL1 and IS_MANAGER_LEVEL 2
-- I'm aware that the below codes are wrong
-- However this means I wanted to insert these values to a row of the cursor if possible
-- Do some logic to know what data will be assigned in the row.
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL1 = 'Y' then
P_CURSOR := <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>
end if;
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL2 = 'Y' then
P_CURSOR := <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>
end if;
END LOOP;
END LOOP;
CLOSE REFCUR;
END GET_PERSONROLES;
A ref cursor is not a variable: it is a pointer to a result set which is consumed by the act of reading it. The result set itself is immutable.
Immutability makes sense, because it reflects Oracle's emphasis on read consistency.
The simplest way to produce the output you appear to want is to create a SQL Type
open P_CURSOR for
select IS_MANAGER_LEVEL1,
IS_MANAGER_LEVEL2
from table ( PERSONROLES_TABLETYPE );
This will work in 12c; in earlier versions to use the table() call like this you may need to declare REFTABLETYPE and TABLETYPE as SQL types( rather than in PL/SQL).
"Ok edited it now"
Alas your requirements are still not clear. You haven't given us the structure of the output ref cursor or shown what other processing you want to undertake.
However, given the title of your question, let's have a guess. So:
create or replace PROCEDURE GET_PERSONROLES ( P_CURSOR IN OUT types.cursorTypePersonRole) AS
REFCUR SYS_REFCURSOR;
TYPE REFTABLETYPE IS RECORD (IS_MANAGER_LEVEL1 VARCHAR2(1),
IS_MANAGER_LEVEL2 VARCHAR2(1));
TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
PERSONROLES_TABLETYPE TABLETYPE;
personrole_rec PersonRole%rowtype;
type personrole_nt is table of PersonRole%rowtype;
personroles_recs personrole_nt := new personrole_nt() ;
BEGIN
MY_STOREDPROC('12345', REFCUR);
FETCH REFCUR BULK COLLECT INTO PERSONROLES_TABLETYPE;
FOR indx IN 1 .. PERSONROLES_TABLETYPE.COUNT LOOP
/* in the absence of requirements I'm just making some stuff up */
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL1 = 'Y' then
personrole_rec.whatever1 := 'something';
else
personrole_recc.whatever1 := null;
end if;
if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL2 = 'Y' then
personrole_rec.whatever2 := 'something else';
else
personrole_recc.whatever2 := null;
end if;
if personrole_rec.whatever1 is not null
or personrole_rec.whatever2 is mot null then
personroles_recs.exend();
personroles_recs(personroles_recs.count()) := personroles_rec;
end if;
END LOOP;
CLOSE REFCUR;
open p_cursor for
select * from table ( personroles_recs );
END GET_PERSONROLES;
This code uses a second collection to store the desired output. Like your code it reads the populated collection and evaluates the attributes of each row. If a value which means the criteria it sets an attribute in a rowtype variable. If one or both attributes are set it populates a new row in a second collection. At the end of the procedure it opens the ref cursor using a table() function call on the second collection.
Note that you do not need the nested loop: you're not using the LIMIT clause so your coder reads the entire cursor into the collection in one swoop.
The implemented rules may not be exactly what you want (because you haven't explained exactly what you want) but this should give you the general idea.
Note that, depending on exactly what processing is masked by <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>, the simpler approach could still be feasible:
open P_CURSOR for
select case when IS_MANAGER_LEVEL1 = 'Y' then 'YES' else 'NO' end,
case when IS_MANAGER_LEVEL2 = 'Y' then 'YES' else 'NO' end
from table ( PERSONROLES_TABLETYPE );

Resources