Oracle create function using cursors - oracle

I have a requirement to create a function in which I have to pass Query result as input to the output query concatenate by space . The below code is roughly written. Need help in modifying the function.
CREATE or replace FUNCTION GETPGM(Year IN Number, ID IN Number)
RETURN VARCHAR2 IS
result VARCHAR2(200);
cursor getterm is
select term_code from table_term
where proc_yr = Year;
BEGIN
loop
fetch cur into TERM;
exit when cur%NOTFOUND;
select f_getp (ID,:TERM1,Year)||' ' f_getp (ID,:TERM2,Year) from dual -- output Result set
end loop;
RETURN result;
END;
Let me know if any doubts.

If you want to apply the f_getp function to every row of the query result and concatenate the results into a space delimited string then you do not need to use a cursor and can use LISTAGG:
CREATE FUNCTION GETPGM(
i_year IN table_term.proc_yr%type,
i_id IN Number
) RETURN VARCHAR2
IS
result VARCHAR2(200);
BEGIN
SELECT LISTAGG(f_getp(i_id, term_code, i_year), ' ') WITHIN GROUP (ORDER BY term_code)
INTO result
FROM table_term
WHERE proc_yr = i_year;
RETURN result;
END;
/

It is unclear what TERM1 and TERM2 are (parameters? If so, you should pass them to the function), nor what is the result supposed to be.
Anyway, see if something like this helps:
CREATE OR REPLACE FUNCTION getpgm (par_year IN NUMBER,
par_id IN NUMBER,
par_term1 IN NUMBER,
par_term2 IN NUMBER)
RETURN VARCHAR2
IS
result VARCHAR2 (200);
BEGIN
FOR cur_r IN (SELECT term_code
FROM table_term
WHERE proc_yr = par_year)
LOOP
result :=
result
|| ' '
|| f_getp (par_id, par_term1, par_year)
|| ' '
|| f_getp (par_id, par_term2, par_year);
END LOOP;
RETURN result;
END;
result should be concatenated with its "previous" value (otherwise, you'd get the last cursor's value as the result, not everything)
use cursor FOR loop as Oracle does all the dirty job for you (you don't have to declare cursor variable, open the cursor, fetch from it, worry about exiting the loop, close the cursor - note that a lot of those things your code doesn't have, while it should)
pay attention to return value's datatype; will a string whose length is 200 characters enough? The result will be a space-separated list of some values. Wouldn't you rather return a ref cursor or a collection?

Related

Making some changes to a function in Oracle

I have a function which is not working as expected. How can I modify to get the desired output?
CREATE OR REPLACE FUNCTION f_get_all_programs_test(
pidm in number,aidy in varchar2
) RETURN VARCHAR2
IS
TERM NUMBER;
result VARCHAR2(300);
CURSOR C
IS
SELECT stvterm_code FROM stvterm
WHERE stvterm_fa_proc_yr = aidy;
BEGIN
Open C;
loop
fetch C into TERM;
exit when C%NOTFOUND;
SELECT LISTAGG(rzkutil.f_get_program (pidm,TERM, aidy), ',') WITHIN GROUP (ORDER BY stvterm.stvterm_code)
INTO result
FROM stvterm stvterm
WHERE stvterm.stvterm_fa_proc_yr = aidy;
end loop;
RETURN result;
END;
Cursor select Query returns multiple rows. For each row, the function rzkutil.f_get_program must run and separate them by comma. The existing code is not working as expected. instead the output is repeating multiple times.
Example:
select rzkutil.f_get_program(12098136,'',2122) from dual -- AAS-PG2-AOTO (result)
select f_get_all_programs_test(12098136,'2122') from dual --AAS-PG2-AOTO,AAS-PG2-AOTO,AAS-PG2-AOTO,AAS-PG2-AOTO,AAS-PG2-AOTO,AAS-PG2-AOTO (result, instead it should return AAS-PG2-AOTO)
As values you're aggregating come from the cursor, it means that its (cursor's) query returns duplicates. To avoid them, use the DISTINCT keyword:
CURSOR C
IS
SELECT DISTINCT stvterm_code
FROM stvterm
WHERE stvterm_fa_proc_yr = aidy;
Though, I believe that you don't need such a complicated (and potentially slow, because of loop processing) code. I don't have any test data to try it, but - see if this helps:
CREATE OR REPLACE FUNCTION f_get_all_programs_test (pidm IN NUMBER,
aidy IN VARCHAR2)
RETURN VARCHAR2
IS
result VARCHAR2 (300);
BEGIN
SELECT LISTAGG (term, ',') WITHIN GROUP (ORDER BY stvterm_code)
INTO result
FROM (SELECT DISTINCT
rzkutil.f_get_program (pidm, stvterm_code, aidy) AS term,
stvterm_code
FROM stvterm
WHERE stvterm_fa_proc_yr = aidy);
RETURN result;
END;

Stored Procedure to get all records

I want to loop thru all the records and concatenate them into one string.
Here is the code:
create or replace PROCEDURE P_GET_TRACKING_NOS
(
P_ORDERID NUMBER,
TRACKINGNOS OUT VARCHAR2
)
IS
CURSOR C1 IS
SELECT TRACKID
FROM MULTISHIPDTL
WHERE ORDERID = P_ORDERID;
BEGIN
TRACKINGNOS := '';
FOR TRACKID_REC IN C1
LOOP
TRACKINGNOS := TRACKINGNOS + ', ' + TRACKID_REC.TRACKID;
END LOOP;
END;
Depending on how long the result is, and if it is shorter than 4000 characters, a simpler option would be to use LISTAGG, e.g.
select listagg(m.trackid, ', ') within group (order by null) result
from multishipdtl m
where m.orderid = p_orderid;
Besides, why is it a procedure? A function seems to be a better option (you can use it in SQL; a procedure with an OUT parameter requires a(n anonymous) PL/SQL block, declaring a variable which accepts the result). For example:
create or replace function f_get_tracking_nos (p_orderid in number)
return varchar2
is
retval varchar2(4000);
begin
select listagg(m.trackid, ', ') within group (order by null)
into retval
from multishipdtl m
where m.orderid = p_orderid;
return retval;
end;

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

Oracle using the like comparison taken from function in value

I am trying to create a simple function that takes in 3 parameters, 2 numbers and a string. I have written the function but am not getting the expected results from a simple select statement when using the LIKE comparison for the string.
The select from the function below returns no rows when executed with the string input value set to ebts, but if I run this as a standalone select state it returns 2 rows which what I would expect. Have used dbms output to determine if whitespace were being passed but all looks OK.
CREATE OR REPLACE FUNCTION OPC_OP.sitezone_exists
(in_site_id IN NUMBER, in_zone_id IN NUMBER, in_mod VARCHAR2)
RETURN NUMBER
IS
v_count_rec NUMBER;
v_return NUMBER;
v_mod VARCHAR2(4) := in_mod;
BEGIN
SELECT COUNT(*)
INTO v_count_rec
FROM AW_ACTIVE_ALARMS
WHERE AW_ACTIVE_ALARMS.site_id = in_site_id
AND AW_ACTIVE_ALARMS.zone_id = in_zone_id
AND AW_ACTIVE_ALARMS.module LIKE 'v_mod%';
IF v_count_rec > 0
THEN
DBMS_OUTPUT.PUT_LINE('count'||v_count_rec||'=========='||v_mod||'=============');
v_return:= 1;
RETURN (v_return);
ELSE
DBMS_OUTPUT.PUT_LINE('count'||v_count_rec||'=========='||v_mod||'=============');
v_return:= 0;
RETURN (v_return);
END IF;
END sitezone_exists;
When passing in values 12, 12, ebts the output displayed is:
count 0 ==========ebts=============
RetVal = 0
If I run the same select subtituting only passing in the above values the query returns 2 rows - I have removed the like clause of the function and it then returns 2 rows, any idea why the like part of clause is failing to match with rows.
You are trying to match a string literal with this:
AND AW_ACTIVE_ALARMS.module LIKE 'v_mod%';
Change it to:
AND AW_ACTIVE_ALARMS.module LIKE v_mod||'%';
MarioAna has the right answer, IMO, but as an aside (which is too long for a comment), your function can be better written.
You're duplicating the dbms_output code, plus it's considered best practice to have a single RETURN in a function (although I would argue that more might be ok - one in the body and one per exception in the exception block...), so you could rewrite it as:
CREATE OR REPLACE FUNCTION OPC_OP.sitezone_exists
(in_site_id IN NUMBER, in_zone_id IN NUMBER, in_mod VARCHAR2)
RETURN NUMBER
IS
v_count_rec NUMBER;
v_return NUMBER;
v_mod VARCHAR2(4) := in_mod;
BEGIN
SELECT COUNT(*)
INTO v_count_rec
FROM AW_ACTIVE_ALARMS aaa
WHERE aaa.site_id = in_site_id
AND aaa.zone_id = in_zone_id
AND aaa.module LIKE v_mod||'%';
DBMS_OUTPUT.PUT_LINE('count '||v_count_rec||'=========='||v_mod||'=============');
IF v_count_rec > 0 THEN
v_return := 1;
ELSE
v_return:= 0;
END IF;
RETURN (v_return);
END sitezone_exists;
/

wrong updation of data using collection but insertion is possible

create or replace function get_ware_house_branch(p_WAREHOUSE_IDS in varchar2,
p_PLACE_ID in varchar2)
return id_warehouse_list
is
l_warehouse_list id_warehouse_list := id_warehouse_list();
str varchar2(300);
begin
str := 'SELECT BRANCH_WAREHOUSE(w.wh_id, w.wh_name)
FROM POD_WAREHOUSE_MASTER W
where ( W.wh_id IN (' ||p_WAREHOUSE_IDS || '))';
execute immediate str bulk collect into l_warehouse_list;
for i in l_warehouse_list.first..l_warehouse_list.last loop
dbms_output.put_line(l_warehouse_list(i).wh_id || ', ' || l_warehouse_list(i).wh_name);
/*update pod_place_warehouse_mapping_tb
set wh_id = l_warehouse_list(i).wh_id
where place_id = p_PLACE_ID ;*/
insert into pod_place_warehouse_mapping_tb (id,place_id ,wh_id )
values
(POD_UNIQUE_VAL_SEQ.NEXTVAL ,p_PLACE_ID,l_warehouse_list(i).wh_id);
end loop;
commit;
return l_warehouse_list;
end;
The result you're seeing is exactly what the commented-out UPDATE statement says you want to do. In the update case you could replace the loop with a single update statement:
UPDATE POD_PLACE_WAREHOUSE_MAPPING_TB
SET WH_ID = l_warehouse_list(l_warehouse_list.LAST).WH_ID
WHERE PLACE_ID = p_PLACE_ID
The loop and the UPDATE statement above will produce the exact same results. In the loop case, you're updating all rows in POD_PLACE_WAREHOUSE_MAPPING_TB with each value from l_warehouse_list, one after the other. When the loop is complete all rows in POD_PLACE_WAREHOUSE_MAPPING_TB which have PLACE_ID = p_PLACE_ID will have their WH_ID column set to the WH_ID value from the l_warehouse_list element with the highest index.
I have no idea what other result you're expecting. I think you need to re-think what it is you're trying to do.
Best of luck.

Resources