Should I use a reference cursor here? - oracle

I'm writing a function which will eventually use one of several possible cursors, for simplicity sake I'll use two here. In the body of the function I want to be able to use a single variable to describe whichever cursor gets used. Do I use a reference cursor for this or something else?
In the example below, I want the FOR LOOP to use either C1 or C2 depending on the value of myChar. So instead of putting an IF (or CASE statement) around the entire FOR LOOP, I want to make what is currently C1 a variable. Is this doable?
CREATE OR REPLACE FUNCTION MY_FUNCTION (myChar IN CHAR) RETURN INTEGER AS
CURSOR C1 IS
SELECT FIRST_NAME
FROM EMPLOYEES;
CURSO C2 IS
SELECT LAST_NAMES
FROM EMPLOYEES;
BEGIN
FOR X IN C1 LOOP
-- DO STUFF
END LOOP;
RETURN 1;
END MY_FUNCION;

Yes, I didn't test this, but it might work:
CREATE OR REPLACE FUNCTION MY_FUNCTION (myChar IN CHAR) RETURN INTEGER AS
type r_cursor is REF CURSOR;
c1 r_cursor;
begin
IF myChar = "1" THEN
open c1 for SELECT FIRST_NAME FROM EMPLOYEES;
ELSE
open c1 for SELECT LAST_NAMES FROM EMPLOYEES;
END IF;
loop
fetch c1 into c1recs;
-- DO STUFF
exit when c1%notfound;
end loop;
close c1;
RETURN 1;
end;

Related

wrong number or types of arguments in call to P_AA

When I try to compile the procedure with collection type as in parameter, I'm getting the error like
wrong number or types of arguments in call to 'P_AA'
-------Procedure created with in parameter as nested table------------
create or replace procedure p_aa(serv in t45)
is
aa serv_item%rowtype;
begin
for i in 1..serv.count
loop
select a.* into aa from serv_item a where a.serv_item_id = serv(i);
dbms_output.put_line('Serv item '||aa.serv_item_id||' '||'status '||aa.status);
end loop;
end;
/
----------Calling the package----------
declare
type t1 is table of number;
cursor c1 is select serv_item_id from serv_item;
n number:=0;
v t1;
begin
open c1;
loop
fetch c1 into v(n);
exit when c1%notfound;
n:=n+1;
end loop;
close c1;
p_aa(v);
end;
/
How can I fix my code?
Your procedure defines the parameter like this:
serv in t45
So t45 is the defined datatype of the parameter.
Now when you call the procedure you pass in a variable v. And how is v defined?
type t1 is table of number;
...
v t1;
t1 is a different type to t45. Even if they have identical structures they are different types. And that's why you get PLS-00306. The solution is quite simple: define v as t45.
i am getting error like 'Reference to uninitialized collection'.
You need to initialise the collection. You do this using the default constructor of the type, either at the start of the program ...
v := t45();
... or when you declare it:
v t45 := t45();
Once you get beyond that you will find your assignment logic is wrong: you're fetching into an element of the collection before you increment the counter or extend the array. So what you need is this:
declare
cursor c1 is select serv_item_id from serv_item;
n number:=0;
v t45 := t45();
x number;
begin
open c1;
loop
fetch c1 into x;
exit when c1%notfound;
n:=n+1;
v.extend();
v(n) := x;
end loop;
close c1;
p_aa(v);
end;
/
Alternatively use the less verbose bulk collect, which handles all the looping and type management implicitly :
declare
v t45;
begin
select serv_item_id
bulk collect into v
from serv_item;
p_aa(v);
end;
/
Here is a db<>fiddle demo showing both approaches working.

how inner cursor use a field of outer cursor in oracle?

I have two nested cursors. I mean one cursor Is Inside of another one.
I want to use a field of outer cursor inside the inner one. something like this:
Inner_cursor.outer_cursor.outer_cursor_column;
but It does not work even I use like this:
Inner_cursor.(outer_cursor.outer_cursor_column);
Is there any way I could do this?
EDIT:
This Is My Code:
CREATE OR REPLACE PROCEDURE TEST1
AS
CURSOR loop_relation IS
SELECT * FROM RELATION_table;
relation_rec loop_relation%rowtype;
CURSOR loop_BIG_TABLE IS
SELECT * FROM BIG_TABLE;
BIG_TABLE_rec loop_BIG_TABLE%rowtype;
BEGIN
FOR RELATION_REC IN LOOP_RELATION
LOOP
FOR BIG_TABLE_rec in loop_BIG_TABLE
LOOP
IF (BIG_TABLE_REC.RELATION_REC.DESTINATION_PK IS NULL) THEN
UPDATE BIG_TABLE
SET BIG_TABLE.RELATION_REC.DESTINATION_PK = (
SELECT RELATION_REC.SOURCE_FK FROM RELATION_REC.SOURCE_TABLE
WHERE RELATION_REC.SOURCE_PK = BIG_TABLE_REC.RELATION_REC.SOURCE_PK)
WHERE BIG_TABLE_REC.ID = BIG_TABLE.ID;
END IF;
END LOOP;
END LOOP;
END TEST1;
/
my problem is in the lines that i use three dot(.) to use a value of outer cursor in inner cursor.
Here's an example of two nested cursors and variables from the outer one used in the inner one. I hope it helps you.
BEGIN
FOR r_outer in (
select tab1.field1
from table1 tab1 )
LOOP
FOR r_inner in (
select tab2.field2
from table2 tab2
where tab2.field2 = r_outer.field1 )
LOOP
dbms_output.put_line(r_outer.field1);
dbms_output.put_line(r_inner.field2);
END LOOP;
END LOOP;
END;
For reference, I created a procedure to display how to use outer cursor value inside inner cursor value. I hope this resolves your query.
CREATE OR REPLACE PROCEDURE cur_inside_cur(my_cur OUT sys_refcursor)
AS
CURSOR roy_cur IS
SELECT name FROM avrajit;
roy_cur1 roy_cur%ROWTYPE;
BEGIN
OPEN roy_cur;
LOOP
FETCH roy_cur INTO roy_cur1;
EXIT WHEN roy_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(roy_cur1.name);
OPEN my_cur FOR
SELECT department FROM avrajit
WHERE name=roy_cur1.name;
END LOOP;
END cur_inside_cur;
OUTPUT
var c refcursor;
begin
cur_inside_cur(:c);
end;
print c;

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 Looping through the values of a cursor

CREATE OR REPLACE Function TotalIncome
( name_in IN varchar2 )
RETURN varchar2
IS
total_val number(6);
cursor c1 is
select monthly_income
from employees
where name = name_in;
BEGIN
total_val := 0;
FOR employee_rec in c1
LOOP
total_val := total_val + employee_rec.monthly_income;
END LOOP;
RETURN total_val;
END;
In this example, I am being told that that we are looping through the values of but what I dont understand is if we are looping through the values why is employee_rec.monthly_income necessary ? Also, when I do a FETCH on employee_rec I seem to be getting an error so I guess they are different from each other.
you are looping through records in the result set.
in other words:
FOR employee_rec in c1
means open the cursor c1 and perform a fetch on it. For each row found, assign the row record to a record variable called employee_rec.
so to reference monthly_income in that, you have to say employee_rec.monthly_income and not just monthly_income on its own.
when I do a FETCH on employee_rec
employee_rec is not a cursor (its a variable) so you don't fetch from it. In this case the fetch from C1 is handled implicitly by the for loop, so no explicit fetch on your part is required.
You are not "looping through the values", you are looping through the records returned by your cursor. In your case, your records only contain one field -- monthly_income -- but in general, a record can contain many fields, which you can use as you see fit in each loop iteration.

How to find number of rows in cursor

I would like to find the number of rows in a cursor. Is there a keyword that can help? Using COUNT, we have to write a query. Any help will be greatly appreciated.
The cursor_variable.%ROWCOUNT is the solution. But its value will be 0 if you check it after opening. You need to loop through all the records, to get the total row count. Example below:
DECLARE
cur sys_refcursor;
cur_rec YOUR_TABLE%rowtype;
BEGIN
OPEN cur FOR
SELECT * FROM YOUR_TABLE;
dbms_output.put_line(cur%rowcount);--returning 0
LOOP
FETCH cur INTO cur_rec;
EXIT WHEN cur%notfound;
dbms_output.put_line(cur%rowcount);--will return row number beginning with 1
dbms_output.put_line(cur_rec.SOME_COLUMN);
END LOOP;
dbms_output.put_line('Total Rows: ' || cur%rowcount);--here you will get total row count
END;
/
You must open the cursor and then fetch and count every row. Nothing else will work.
You can also use BULK COLLECT so that a LOOP is not needed,
DECLARE
CURSOR c
IS SELECT *
FROM employee;
TYPE emp_tab IS TABLE OF c%ROWTYPE INDEX BY BINARY_INTEGER;
v_emp_tab emp_tab;
BEGIN
OPEN c;
FETCH c BULK COLLECT INTO v_emp_tab;
DBMS_OUTPUT.PUT_LINE(v_emp_tab.COUNT);
CLOSE c;
END;
/
Edit: changed employee%ROWTYPE to c%ROWTYPE
You can use following simple single line code to print cursor count
dbms_output.put_line(TO_CHAR(cur%rowcount));
This should work for you
DECLARE
CURSOR get_data_ IS
SELECT *
FROM table_abc_
WHERE owner = user_; -- your query
counter_ NUMBER:= 0;
BEGIN
FOR data_ IN get_data_ LOOP
counter_ := counter_ + 1;
END LOOP;
dbms_output.put_line (counter_);
END;
DECLARE #STRVALUE NVARCHAR(MAX),
#CREATEDDATE DATETIME,
#STANTANCEVALUE NVARCHAR(MAX),
#COUNT INT=0,
#JOBCODE NVARCHAR(50)='JOB00123654',
#DATE DATETIME=GETDATE(),
#NAME NVARCHAR(50)='Ramkumar',
#JOBID INT;
CREATE TABLE #TempContentSplitValue (ITEMS NVARCHAR(200))
SELECT #JOBID = i.Id FROM JobHeader_TBL i WHERE Id=1201;
IF EXISTS (SELECT 1 FROM JobHeader_TBL WHERE Id=#JOBID)
BEGIN
SELECT #STRVALUE= Description from ContentTemplate_TBL where Id=1
INSERT INTO #TempContentSplitValue SELECT * FROM dbo.split(#STRVALUE, '_')
SET #STRVALUE=''
DECLARE db_contentcursor CURSOR FOR SELECT ITEMS FROM #TempContentSplitValue
OPEN db_contentcursor
FETCH NEXT FROM db_contentcursor
INTO #STANTANCEVALUE
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #STRVALUE += #STANTANCEVALUE + 'JOB00123654'
SET #COUNT += 1
SELECT #COUNT
FETCH NEXT FROM db_contentcursor INTO #STANTANCEVALUE
END
CLOSE db_contentcursor
DEALLOCATE db_contentcursor
DROP TABLE #TempContentSplitValue
SELECT #STRVALUE
END
Here I am trying to count the total number of customers with age greater than 25. So store the result in the cursor first. Then count the size of the cursor inside the function or in the main begin itself.
DECLARE
cname customer24.cust_name%type;
count1 integer :=0;
CURSOR MORETHAN is
SELECT cust_name
FROM customer24
where age>25;
BEGIN
OPEN MORETHAN;
LOOP
FETCH MORETHAN into cname;
count1:=count1+1;
EXIT WHEN MORETHAN%notfound;
END LOOP;
-- dbms_output.put_line(count1);
dbms_output.put_line(MORETHAN%ROWCOUNT);
CLOSE MORETHAN;
END;
There is a possible work around that may be useful/needed because of the overhead of accessing a database server over a network (e.g., when using Ajax calls)
Consider this:
CURSOR c_data IS
SELECT per_first_name , null my_person_count
FROM person
UNION
SELECT null as per_first_name , count( distinct per_id ) as my_person_count
FROM person
order by my_person_count ;
The first row fetched has the count of records. One MUST add specific columns fetched (the use of the * does not work), and one can add additional filters.
Try this:
print(len(list(cursor)))
I always read that people loop through results. Why not using a count(*)?
An example from my production code:
PROCEDURE DeleteStuff___(paras_ IN Parameters_Type_Rec)
IS
CURSOR findEntries_ IS
select * from MyTable
where order_no = paras_.order_no;
counter_ NUMBER;
CURSOR findEntries_count_ IS
SELECT COUNT(*) from MyTable
where order_no = paras_.order_no;
BEGIN
OPEN findEntries_count_;
FETCH findEntries_count_ INTO counter_;
CLOSE findEntries_count_;
dbms_output.put_line('total records found: '||counter_);
IF (counter_ = 0) THEN
-- log and leave procedure
RETURN;
END IF;
FOR order_rec_ IN findEntries_ LOOP
EXIT WHEN findEntries_%NOTFOUND OR findEntries_%NOTFOUND IS NULL;
-- do stuff - i.e. delete a record.
API_Package.Delete(order_rec_);
END LOOP;
END DeleteStuff___;
If the query is small, that is my prefered way.
In this example, I just want to know (and log) how many entries I'll delete.
p.s. Ignore the three underlines. In IFS, this is used when you want private procedures or functions.
You can’t have cursor count at start. For that you need to fetch complete cursor; that is the way get cursor count.
declare
cursor c2 is select * from dept;
var c2%rowtype;
i number :=0;
begin
open c2;
loop
fetch c2 into var;
exit when c2%NOTFOUND;
i: = i+1;
end loop;
close c2;
dbms_output.put_line('total records in cursor'||i);
end;
You can use %ROWCOUNT attribute of a cursor.
e.g:
DECLARE
CURSOR lcCursor IS
SELECT *
FROM DUAL;
BEGIN
OPEN lcCursor ;
DBMS_OUTPUT.PUT_LINE(lcCursor%ROWCOUNT);
CLOSE lcCursor ;
END;

Resources