Multi-Row Function - oracle

How to return a multi-row data from a function with cursor ?
Create or Replace function Get_Course_details(Stud_ID number)
return varchar2
Is
cursor C_1 is
select d.desc_english
from course_details d ,course_track c , students s
where D.COURSE_TRACK_ID = C.COURSE_TRACK_ID
and C.COURSE_TRACK_ID = s.STUDENT_ID
and s.student_ID=stud_ID;
cursor C_2 is
select count(d.desc_english)
from course_details d ,course_track c , students s
where D.COURSE_TRACK_ID = C.COURSE_TRACK_ID
and C.COURSE_TRACK_ID = s.STUDENT_ID
and s.student_ID=stud_ID;
var_1 varchar2(1000);
var_2 number;
Begin
open c_2;
fetch c_2
into var_2;
close c_2;
for x in (select d.desc_english
from course_details d ,course_track c , students s
where D.COURSE_TRACK_ID = C.COURSE_TRACK_ID
and C.COURSE_TRACK_ID = s.STUDENT_ID
and s.student_ID=stud_ID)
loop
open c_1;
fetch c_1
into var_1;
close c_1;
return var_1;
end loop;
end;

Okay, so as Justin pointed out there are few issues with your current function:
var_2 is never used.
You are trying to return multirow data, however, the function will return var_1 to the calling procedure on the very first iteration.
The below solution assumes you are trying to get an array of strings from a cursor that returns many rows. It is a generic demonstration, you will have modify it for you particular instance.
CREATE OR REPLACE FUNCTION string_array_from_cursor
RETURN dbms_sql.Varchar2_Table
IS
--Obviously this could be _ANY_ select query
CURSOR cursorToReturnManyRows IS
SELECT o.object_name
FROM user_objects o
WHERE o.OBJECT_TYPE = 'TABLE';
--This is an inbuilt index-by table (you could also use a varray,
-- associative array or nested table)
stringArray dbms_sql.Varchar2_Table;
BEGIN
--Use a FOR loop to iterate through the cursor,
-- at each iteration add the value as the next item in the array
FOR rec IN cursorToReturnManyRows LOOP
stringArray(stringArray.count + 1) := rec.object_name;
END LOOP;
--After the loop is complete return the array
RETURN stringArray;
END;
I suggest you compile the above function, and run it the below test window to see how it can apply to your instance.
DECLARE
stringArray dbms_sql.Varchar2_Table;
BEGIN
stringArray := string_array_from_cursor;
dbms_output.put_line(stringArray.count);
FOR i IN 1..stringArray.count LOOP
dbms_output.put_line(stringArray(i));
END LOOP;
END;

As an alternative you can return an opened SYS_REFCURSOR and let the caller fetch from the cursor:
CREATE OR REPLACE FUNCTION GET_COURSE_DETAIL_CURSOR(STUD_ID NUMBER)
RETURN SYS_REFCURSOR
IS
C_1 SYS_REFCURSOR;
BEGIN
OPEN C_1 FOR SELECT d.DESC_ENGLISH
FROM STUDENTS s
INNER JOIN COURSE_TRACK c
ON c.COURSE_TRACK_ID = s.STUDENT_ID
INNER JOIN COURSE_DETAILS d
ON d.COURSE_TRACK_ID = c.COURSE_TRACK_ID
WHERE S.STUDENT_ID = STUD_ID;
RETURN C_1;
END GET_COURSE_DETAIL_CURSOR;
The caller would, of course, be responsible for closing the cursor.
(And BTW - the join criteria c.COURSE_TRACK_ID = s.STUDENT_ID seems to be contradictory with the later join criteria d.COURSE_TRACK_ID = c.COURSE_TRACK_ID, but on the other hand I don't know your data. It just looks odd to me).
Share and enjoy.

Related

What type to choose for the return value of the function

I want to return query results from a function:
SELECT Lec.univ_id,Count(Lec.lecturer_id) FROM D8_SUBJECT Sub
JOIN D8_SUBJ_LECT SubLect ON
Sub.subj_id = SubLect.subj_id
JOIN D8_LECTURER Lec ON
SubLect.LECTURER_ID = Lec.LECTURER_ID
WHERE Sub.subj_name = 'ИНФОРМАТИКА' AND univ_id BETWEEN 1 AND 50
Group BY Lec.univ_id;
Where is the result of this query of a row of the form (id Integer, count Integer).
I tried something like that
CREATE OR REPLACE FUNCTION GetMaximum
(first_univer IN Integer,second_univer IN Integer,subj_name IN NVARCHAR(30))
RETURN user_tables.num_rows%TYPE
AS
rf_cur sys_refcursor;
BEGIN
OPEN rf_cur for
SELECT Lec.univ_id,Count(Lec.lecturer_id)
FROM D8_SUBJECT Sub
JOIN D8_SUBJ_LECT SubLect
ON Sub.subj_id = SubLect.subj_id
JOIN D8_LECTURER Lec
ON SubLect.LECTURER_ID = Lec.LECTURER_ID
WHERE Sub.subj_name = subj_name AND univ_id BETWEEN first_univer AND second_univer
Group BY Lec.univ_id;
return rf_cur;
END GetMaximum;
/
but it don't compile. What type do I need to use for the return value
If you're returning refcursor, then function declaration has to support it:
CREATE OR REPLACE FUNCTION GetMaximum
(first_univer IN Integer,second_univer IN Integer,subj_name IN NVARCHAR(30))
RETURN sys_refcursor --> this
AS
rf_cur sys_refcursor; --> this
BEGIN
OPEN rf_cur for
SELECT Lec.univ_id,Count(Lec.lecturer_id) cnt
FROM D8_SUBJECT Sub
<snip>
Group BY Lec.univ_id;
return rf_cur; --> this
END GetMaximum;
/

PL/sql function return an error of many rows

This function is returning only the VAC_NAME (but not the list of these names)
create or replace function FCT_VAC(NO_VAC in number)
return varchar2
is
V_lNAME varchar2(30);
begin
select u.UTI_NAME ||' '|| u.UTI_L_NAME into V_lNAME
from USER_TAB u
join DEAL_TAB d on u.USERNAME = d.USERNAME
;
return V_lNAME;
end FCT_VAC_NAME;
/
set serveroutput on;
select FCT_VAC_NAME(3) as Vacantion_Name from dual;
but if I will simple select
select u.UTI_NAME, u.UTI_L_NAME
from USER_TAB u
join DEAL_TAB d on u.USERNAME = d.USERNAME
;
It works as expected, what is the problem of function?
Query returns two (or more) rows; they can't fit into a scalar v_lname varchar2(30) variable.
Question is: what result do you expect? Only one value? Then make sure that query returns it; WHERE clause might help (especially as you pass a parameter but never use it), e.g.
select u.UTI_NAME ||' '|| u.UTI_L_NAME
into V_lNAME
from USER_TAB u
join DEAL_TAB d on u.USERNAME = d.USERNAME
where u.no_vac = p_no_vac; --> this
Note that I've renamed parameter; I don't know whether any of those tables contain no_vac column, but - if it does - you'd better use a different name for the parameter. One option is to prefix it with p_.
Otherwise, rewrite the function so that it returns something else (custom type, table, whatever).

PL/SQL function while loop returning 1 row only

I have tables(Customers,Pbasket and pp) with the following values inserted to the table
http://pastebin.com/eMUtLJn9
Basically I am tasked to create a function that finds all the product number(p#) purchased by the customer and I have to pass in the customer id(c#) as a input parameter;
This is my pl/sql script
http://pastebin.com/SqkY0P9N
I noticed that
there is no results returned for c#(100), which is correct.
but i noticed that for c#(101) and c#(102)
the result should return more than one p# but it only returns 1 result even though I had my while loop.
the output of my results
Please advice.
The function you wrote was supposed to return One row only. And also, you had a return in your LOOP. So after first iteration the control is returned back.(leaving the cursor opened for ever)
Create a TYPE of SQL Object
create type my_numbers as table of NUMBER;
/
FUNCTION returning a table!
CREATE OR REPLACE FUNCTION purchased(cId IN NUMBER)
RETURN my_numbers
IS
product VARCHAR2(45);
I NUMBER;
v_my_list my_numbers := my_numbers();
CURSOR CursorRow IS
SELECT p#
FROM customer c
LEFT OUTER JOIN pbasket pb
ON c.c# = pb.c#
LEFT OUTER JOIN pp pp
ON pb.whenfinalised = pp.whenfinalised
WHERE c.c# = cId;
CurrentPos CursorRow%ROWTYPE;
BEGIN
OPEN CursorRow;
I := 1;
FETCH CursorRow into CurrentPos;
LOOP
EXIT WHEN CursorRow%NOTFOUND
v_my_list.EXTEND;
v_my_list(I) := CurrentPos.p#;
I := I + 1;
END LOOP;
CLOSE CursorRow;
RETURN v_my_list;
END purchased;
/
Final SELECT Query:
select * FROM TABLE(CAST(purchased(100) as my_numbers));
We can also use Pipelined functions (Slight modification needed) for performance over Large resultsets!

bind array in cursor using plsql

declare
cursor lc is
select *
from (select a.lin, a.pr,
b.name, sum(a.up) as u,
sum (a.d) as d
from li_dy_4 a,
p_list b
where a.pr=b.id
and b.parent_id != 0
and a.partitionid <= 308
and a.partitionid >= 302
and a.pr in (91,80)
GROUP BY a.pr, b.name, a.lin
order by d desc) ;
rec lc%ROWTYPE;
BEGIN
open lc;
loop
FETCH lc into rec;
dbms_output.put_line(rec.pr);
exit when lc%NOTFOUND;
end loop;
close lc;
END;
the above statement works fine for me. What I am not capable of finding anything hopeful is changing the value after the "in" statement which is a.pr in (91,80)
I have listed the values here manually, but I want to pass it to the cursor as an array of numbers for a.pr column. In short I want to do a.pr = idlist wher idlist is an array. Please anyone tell me if my idea is possible.
Just want to remind you, the IN clause supports 1000 items only. And that could be the primary reason ,there's nothing called BULK BINDING for SELECT Queries. We have FORALL INSERT/UPDATE, which is like BULK BINDING. Unfortunately select has none.
But still you can achieve your requirement in a different fashion.
You can try a global temporary table(GTT) which a temporary table with "scope of the data inserted" is only to that session.
You can FORALL INSERT all your data for IN clause into that table, and join the TABLE to your Query.
Else you can have a nested table (if oracle 10g) or a simple pl/sql type itself (if oracle 11g), with all your IN class items as records and join it to your Query.
Example: Using NESTED TABLE , effective for less number(<10000) of items
CREATE TYPE pr AS OBJECT
(pr NUMBER);
/
CREATE TYPE prList AS TABLE OF pr;
/
declare
myPrList prList := prList ();
cursor lc is
select *
from (select a.lin, a.pr,
b.name, sum(a.up) as u,
sum (a.d) as d
from li_dy_4 a,
p_list b,
TABLE(CAST(myPrList as prList)) my_list
where a.pr=b.id
and b.parent_id != 0
and a.partitionid <= 308
and a.partitionid >= 302
and a.pr = my_list.pr
GROUP BY a.pr, b.name, a.lin
order by d desc) ;
rec lc%ROWTYPE;
BEGIN
/*Populate the Nested Table, with whatever collection you have */
myPrList := prList ( pr(91),
pr(80));
/*
Sample code: for populating from your TABLE OF NUMBER type
FOR I IN 1..your_input_array.COUNT
LOOP
myPrList.EXTEND;
myPrList(I) := pr(your_input_array(I));
END LOOP;
*/
open lc;
loop
FETCH lc into rec;
exit when lc%NOTFOUND; -- Your Exit WHEN condition should be checked afte FETCH iyself!
dbms_output.put_line(rec.pr);
end loop;
close lc;
END;
/
I don't know the exact structure of your global table but you can use the collection in cursor like this
declare
cursor c1 is
select last_name ls from empc;
type x is table of employees.last_name%type;
x1 x := x();
cnt integer :=0;
begin
for z in c1 loop
cnt := cnt +1;
x1.extend;
x1(cnt) := z.ls;
if x1(cnt) is NULL then-----------
DBMS_OUTPUT.PUT_LINE('ASHISH');
end if;
dbms_output.put_line(cnt || ' '|| x1(cnt));
end loop;
end;

Return multiple values from an Oracle function

I need to return several varchar variables from an Oracle function but when it returns something the function exits immediately. Is there any way I can do this?
Here I return the first value that matches the condition but I want to return all of them.
create or replace function f_pilotosContinentes return varchar as
cursor cc(codigoPiloto int) is
select count(*) as numero from(
select distinct pi.codigo, c.nome from Voo v
join Rota r on r.codigo = v.rota
join Aeroporto a on a.codigo = r.localDestino
join Pais p on p.codigo = a.pais
join Continente c on c.codigo = p.continente
join PilotoVoo pv on pv.voo = v.codigo
join Piloto pi on pi.codigo = pv.piloto
where pi.codigo = codigoPiloto);
pilotos cc%rowtype;
cursor cc1 is
select p.codigo from Piloto p;
codPilotos cc1%rowtype;
duracao int;
temp int;
piloto varchar(20);
begin
duracao := 0;
open cc1;
loop
fetch cc1 into codPilotos;
exit when cc1%notfound;
--Verificar para cada piloto
open cc(codPilotos.codigo);
loop
fetch cc into pilotos;
exit when cc%notfound;
if pilotos.numero between 3 and 4 then
--Buscar a duração total dos voos dele
select sum(v.duracao) into temp from Voo v
join PilotoVoo pv on pv.voo = v.codigo
join Piloto p on p.codigo = pv.piloto
where p.codigo = codPilotos.codigo;
if temp > duracao or duracao = 0 then
duracao := temp;
end if;
end if;
end loop;
close cc;
end loop;
close cc1;
open cc1;
loop
fetch cc1 into codPilotos;
exit when cc1%notfound;
--Verificar para cada piloto
open cc(codPilotos.codigo);
loop
fetch cc into pilotos;
exit when cc%notfound;
if pilotos.numero >= 3 then
--Buscar a duração total dos voos dele
select sum(v.duracao) into temp from Voo v
join PilotoVoo pv on pv.voo = v.codigo
join Piloto p on p.codigo = pv.piloto
where p.codigo = codPilotos.codigo;
if temp = duracao then
select p.nome into piloto from Piloto p
where p.codigo = codPilotos.codigo;
return piloto;
end if;
end if;
end loop;
close cc;
end loop;
close cc1;
dbms_output.put_line('Nenhum piloto viajou para todos os continentes.');
return null;
end;
There are a number of ways you can return multiple strings from a PL/SQL method:
Populate and return a VARCHAR2 array instead of a single VARCHAR2
Use a procedure instead with multiple OUT parameters (suitable if there are a fixed number of return values you want to return, especially if they have different meanings - e.g. name, address, state, etc.)
Use a view instead. This would require transforming the procedural logic from your function into SQL predicates.
Side note: if the function is changed to return an array, you can then if you wish change it to a pipelined function, which would mean that if you call it from a SQL query, it will begin consuming rows as soon as the function returns them, instead of waiting for the function to finish. This may help with performance (both speed and memory usage).

Resources