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).
Related
I am trying to make this work but no matter what I do the od_total var is always empty. I just cannot make it work.
I am trying to run a cursor to get a list of ordersid and save it into o_id every loop. Then, I use the second cursor to use the value stored into o_id to do another query that stores the result into od_total. While o_id seems to be storing the values, od_total for some reason is always empty.
CREATE OR REPLACE PROCEDURE p_practicum2_practice IS
o_id CHAR(5);
od_total NUMBER := 0;
CURSOR ordersid IS
SELECT DISTINCT orderdetails.orderid FROM orderdetails
INNER JOIN orders ON orderdetails.orderid = orders.orderid
INNER JOIN customers ON orders.customerid = customers.customerid
WHERE orders.customerid = 'LILAS';
CURSOR total IS
SELECT SUM(unitprice*quantity) FROM orderdetails WHERE orderid = o_id;
BEGIN
OPEN ordersid;
OPEN total;
LOOP
FETCH ordersid INTO o_id;
EXIT WHEN ordersid%notfound;
LOOP
FETCH total INTO od_total;
EXIT WHEN total%notfound;
END LOOP;
dbms_output.put_line(o_id || ' ---- ' || od_total);
END LOOP;
CLOSE ordersid;
CLOSE total;
END;
this is what I get as result:
enter image description here
As far i could understand your question you wanted to display od_total for all od_id passed from cursor ordersid. You are not getting anything because od_id is not getting passed to your second cursor. Read the code below inline comments how to pass the argument to cursor.
CREATE OR REPLACE PROCEDURE p_practicum2_practice
IS
o_id CHAR(5);
od_total NUMBER := 0;
CURSOR ordersid
IS
SELECT DISTINCT orderdetails.orderid
FROM orderdetails
INNER JOIN orders
ON orderdetails.orderid = orders.orderid
INNER JOIN customers
ON orders.customerid = customers.customerid
WHERE orders.customerid = 'LILAS';
CURSOR total(v_oid char)
IS
SELECT SUM(unitprice*quantity)
FROM orderdetails
WHERE orderid = v_oid;
BEGIN
OPEN ordersid;
LOOP
FETCH ordersid INTO o_id;
EXIT WHEN ordersid%notfound;
OPEN total(o_id); --<--This is how you pass the paramater to your cursor
LOOP
FETCH total INTO od_total;
EXIT WHEN total%notfound;
dbms_output.put_line(o_id || ' ---- ' || od_total);
END LOOP;
CLOSE total;
--If you print here then only the last od_total value will be printed.
--dbms_output.put_line(o_id || ' ---- ' || od_total);
END LOOP;
CLOSE ordersid;
END;
Output:
17465 ---- 6
12345 ---- 3
14435 ---- 4
19045 ---- 6
19345 ---- 8
Hi I'm trying the code the below logic..i need help..
When i run the following procedure i get all the sids along with the corresponding pid for a particular month. but when i uncomment the parts that i have commented here i get the month and year displayed and then a message saying 'no data found'. Where am i going wrong?
create or replace PROCEDURE mas(V_MONTH NUMBER DEFAULT NULL,V_YEAR NUMBER DEFAULT NULL,V_AID VARCHAR2) AS
V_MID VARCHAR2(50);
V_SID VARCHAR2(50);
v_v_month number := nvl(V_MONTH,to_number(to_char(sysdate,'mm')));
v_v_year number := nvl(V_YEAR,to_number(to_char(sysdate,'yyyy')));
v_is_sub PM.IS_SUB%TYPE;
V_DIST_s NUMBER;
V_DIST_t NUMBER;
cursor c1
is
select distinct a.mid,
b.sid
from pmt a
inner join smt b
on (a.mid = b.mid)
where a.AID = V_AID
AND A.AID = B.AID
AND EXTRACT(MONTH FROM A.RDATE)= v_v_month AND
EXTRACT(YEAR FROM A.RDATE)= v_v_year
order by mid;
BEGIN
dbms_output.put_line('month : ' || v_v_month);
dbms_output.put_line('year : ' || v_v_year);
/*
select IS_SUB into v_is_sub from program_master where pid = 'V_AID';
IF v_is_sub = 1 then
select count(*) into V_DIST_S from (select distinct sid from smt where aid = 'v_aid');
select count(*) into V_DIST_T from (select distinct sid from tm where aid = 'v_aid');
if(V_DIST_S = V_DIST_T) then
*/
for rec1 in c1
loop
dbms_output.put_line('MID : ' || rec1.MID);
dbms_output.put_line('SID : ' || rec1.SID);
end loop;
-- else
-- dbms_output.put_line('count of sids do not match');
--end if;
--else
--dbms_output.put_line('No sids available for the mentioned pid.');
--end if;
END MAS;
where pid = 'V_AID';
V_AID is a variable, however, you have enclosed it within single-quotation marks, which makes it a string. So, you are actually looking for the value 'V_AID' and not the value of the variable.
Modify it to:
select IS_SUB into v_is_sub from program_master where pid = V_AID;
And do the same wherever you have enclosed the variable in single-quotation marks.
Not sure if this is possible, but I'm trying to see if I can convert this procedure to become a view because we've been having trouble with drives not populating the table when the procedure is run.
I'm trying to understand someone else's code and because of the cursors, I'm not even sure we can change this procedure to a view.
----------------------------------------------------------------------
--This Procedure will interface drive information on a nightly basis--
----------------------------------------------------------------------
Procedure HEMA_DRIVE_AUTO IS
v_start_date DATE := trunc(sysdate) -30;
v_end_date DATE := trunc(sysdate);
v_delete_stats_dt DATE := trunc(sysdate)-120;
v_total_registration_count NUMBER;
v_total_performed_count NUMBER;
v_total_collected_count NUMBER;
v_total_deferred_count NUMBER;
v_total_qns_count NUMBER;
v_existing_drive NUMBER;
v_existing_performed NUMBER;
v_maph_drive NUMBER;
--This Cursor will collect the initial data
cursor c_drive_info is
select dr.drive_id, dr.Start_time, dr.vehicle_id
from drives dr
--where dr.drive_id in(1605606);
where trunc(dr.start_time) between v_start_date and v_end_date;
--This Cursor will be used to decode the Donation Types
cursor c_procedure_codes is
select * from hema_donation_type_map hdt
where hdt.mobiles = 1 order by procedure_code_id;
--This Cursor will define the intentions but exclude theraputics inthe mapping
cursor c_intention is
select rsa_motivation_id,hema_intent_id from hema_intent_map
where rsa_motivation_id <> 4 order by rsa_motivation_id;
BEGIN
-- delete records older then 4 months
delete from hema_nightly h where trunc(h.drive_date) < v_delete_stats_dt;
commit;
FOR cur_drive IN c_drive_info LOOP
delete from hema_nightly where drive_id = cur_drive.drive_id;
commit;
-- Loop by motivation/intention
FOR cur_intent in c_intention LOOP
-- Loop to get the procedure code data
FOR cur_proc_code IN c_procedure_codes LOOP
v_total_registration_count := 0;
v_total_performed_count := 0;
v_total_collected_count := 0;
v_total_deferred_count := 0;
v_total_qns_count := 0;
v_maph_drive := 0;
-- get the count for all other procedures
select count(1)
into v_total_registration_count
from registration r
where r.drive_id = cur_drive.drive_id
and r.donation_type_id = cur_proc_code.donation_type_id
and r.motivation_id = cur_intent.rsa_motivation_id;
--get the deferral count
select count(unique(r.registration_id))
into v_total_deferred_count
from registration r
where r.drive_id = cur_drive.drive_id
and r.donation_type_id = cur_proc_code.donation_type_id
and r.motivation_id = cur_intent.rsa_motivation_id
and r.step_completed < 12
and exists (select rsc.registration_id
from reg_steps_completed rsc
where rsc.registration_id = r.registration_id
and rsc.collection_step_id = 99);
-- QNS count
select count(unique(r.registration_id))
into v_total_qns_count
from registration r
where r.drive_id = cur_drive.drive_id
and r.step_completed < 12
and not exists (select rsc.registration_id
from reg_steps_completed rsc
where rsc.registration_id = r.registration_id
and rsc.collection_step_id = 99)
and r.donation_type_id = cur_proc_code.donation_type_id
and r.motivation_id = cur_intent.rsa_motivation_id;
-- performed count is the difference between total registrations and total deferrals.
v_total_performed_count := v_total_registration_count -
(v_total_deferred_count +
v_total_qns_count);
-- not calulatind yield so keep count the same
v_total_collected_count := v_total_performed_count;
-- does this drive exist
select count(drive_id)
into v_existing_drive
from hema_nightly
where drive_id = cur_drive.drive_id
and procedure_id = cur_proc_code.procedure_code_id
and intent = cur_intent.hema_intent_id;
-- Is this an aph vehicle?
select count(vehicle_id)
into v_maph_drive
from vehicles
where veh_drive_type_uid = 2
and vehicle_id = cur_drive.vehicle_id;
if v_existing_drive > 0 then
update hema_nightly
set performed = performed + v_total_performed_count,
collected = collected + v_total_collected_count,
registered = registered + v_total_registration_count,
deferrals = deferrals + v_total_deferred_count,
qns = qns + v_total_qns_count,
drive_date = cur_drive.start_time,
mod_date = sysdate,
intent = cur_intent.hema_intent_id,
aph = v_maph_drive
where drive_id = cur_drive.drive_id
and procedure_id = cur_proc_code.procedure_code_id
and intent = cur_intent.hema_intent_id;
commit;
elsif v_existing_drive = 0 and v_total_registration_count > 0 then
insert into hema_nightly
(drive_id,
procedure_id,
performed,
collected,
registered,
deferrals,
qns,
drive_date,
mod_date,
intent,
aph)
values
(cur_drive.drive_id,
cur_proc_code.procedure_code_id,
v_total_performed_count,
v_total_collected_count,
v_total_registration_count,
v_total_deferred_count,
v_total_qns_count,
trunc(cur_drive.start_time),
sysdate,
cur_intent.hema_intent_id,
v_maph_drive);
commit;
end if;
v_existing_drive := 0;
end loop;
end loop;
end loop;
end hema_drive_auto;
Views don't perform DML (insert, update, delete) and they don't manage transactions with COMMIT and ROLLBACK; they only select and retrieve data.
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.
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;