Making some changes to a function in Oracle - 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;

Related

Oracle create function using cursors

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?

Convert SQL Server code to Oracle function

CREATE FUNCTION dbo.Alphaorder (#str VARCHAR(50))
returns VARCHAR(50)
BEGIN
DECLARE #len INT,
#cnt INT =1,
#str1 VARCHAR(50)='',
#output VARCHAR(50)=''
SELECT #len = Len(#str)
WHILE #cnt <= #len
BEGIN
SELECT #str1 += Substring(#str, #cnt, 1) + ','
SET #cnt+=1
END
SELECT #str1 = LEFT(#str1, Len(#str1) - 1)
SELECT #output += Sp_data
FROM (SELECT Split.a.value('.', 'VARCHAR(100)') Sp_data
FROM (SELECT Cast ('<M>' + Replace(#str1, ',', '</M><M>') +
'</M>' AS XML) AS Data) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)) A
ORDER BY Sp_data
RETURN #output
END
SELECT dbo.Alphaorder ('juan') --> ajnu
That looks like a badly written function to begin with.
If you need to do something like that function does in Oracle SQL, you can do it directly with Oracle SQL features, you don't need to write your own function.
Even if for some reason you do need to write a function, it is perhaps easiest to let SQL do the work for you (the same as you would do if your context was straight SQL).
Something like this:
create or replace function alphaorder(str varchar2) return varchar2
as
output varchar2(4000);
begin
select listagg(ch) within group (order by ch)
into output
from ( select substr(str, level, 1) as ch
from dual
connect by level <= length(str)
)
;
return output;
end;
/
Note that in Oracle you can't limit the length of the input or of the output string; you can only declare the data type. Then in the function itself you can check length and throw an error if the input is longer than 50 characters, but why bother? Let the function work in full generality.
Here's how you would call the function (and check that it works as required):
select alphaorder('juan') as alpha_ordered from dual;
ALPHA_ORDERED
-------------
ajnu

Oracle single row into variable

I need to get all result in one row... it is working but when i want to see it in dbms there is nothink.. why ?
CREATE OR REPLACE PROCEDURE NXMESEP.SP_IN_CHECK_AND_SEND_SMS
( RC_TABLE0 OUT SYS_REFCURSOR,
RS_CODE OUT VARCHAR2, -- RETURN 코드
RS_MSG OUT VARCHAR2
) IS ERROR_EXCEPTION EXCEPTION;
BEGIN
begin
DECLARE
promena varchar2(32767);
BEGIN
OPEN RC_TABLE0 FOR
SELECT listagg(ITEM_ID,', ') within group(order by ITEM_ID)
INTO promena
FROM TB_PL_M_WRKORD WRKOD
WHERE 1 = 1
AND WO_DATE = '20181012'
AND WRKOD.ITEM_ID NOT IN (SELECT ITEM_ID FROM TB_CM_M_FERT_COST_CHK FERT)
AND WC_ID = 'U';
LOOP
FETCH rc_table0 INTO promena;
EXIT WHEN rc_table0%NOTFOUND;
dbms_output.put_line(promena);
END LOOP;
CLOSE rc_table0;
end;
EXCEPTION
.... END;
RS_CODE := 'S'; RS_MSG := 'Complete successfully!';
RETURN; END SP_CHECK_AND_SEND_SMS; /
This should be promena that i expected..
" 12993NXUA, 13595NXUA, 14495NXUA, 16589NX, 16589NX, 16590NX, 16590NX, 16622NX, 16622NX "
Now it is working but im getting unknown error ORA-65535 every time when i execute. But after this I can see dbms result is ok.
Assuming your real code has RC_TABLE0 declared, as a ref cursor, then your variable ends up null because opening the cursor into something doesn't really do anything. You can't open a cursor and select something from the cursor query into a separate variable at the same time, whichever way round you try to do it. You need either a cursor, or a simple select ... into:
DECLARE
promena varchar2(32767);
BEGIN
SELECT listagg(ITEM_ID,', ') within group (order by ITEM_ID)
INTO promena
FROM TB_PL_M_WRKORD WRKOD
WHERE 1 = 1
AND WO_DATE = '20181012'
AND WRKOD.ITEM_ID NOT IN (SELECT ITEM_ID FROM TB_CM_M_FERT_COST_CHK FERT)
AND WC_ID = 'U';
dbms_output.put_line('test: '||promena);
END;
/
test: 12993NXUA, 13595NXUA, 14495NXUA ...
PL/SQL procedure successfully completed.
You also have to set serveroutput on or equivalent to actually see the results, of course.
I've also removed the redundant distinct, the unnecessary select .. from dual - which seemed to be part of the odd cursor construct - and the extra level of begin/end.
Incidentally, your code implies that wo_date is a string, which seems unlikely, or at least not ideal. If it is actually a real date then you should not be using a string for the comparison as you're forcing implicit conversions; use an actual date instead, maybe as an ANSI date literal:
AND WO_DATE = DATE '2018-10-12'
If you did really want to use an explicit cursor approach you would need to use a loop to populate the string variable:
DECLARE
promena varchar2(32767);
rc_table0 sys_refcursor;
BEGIN
OPEN rc_table0 FOR
SELECT DISTINCT listagg(ITEM_ID,', ') within group (order by ITEM_ID)
FROM TB_PL_M_WRKORD WRKOD
WHERE 1 = 1
AND WO_DATE = '20181012'
AND WRKOD.ITEM_ID NOT IN (SELECT ITEM_ID FROM TB_CM_M_FERT_COST_CHK FERT)
AND WC_ID = 'U';
LOOP
FETCH rc_table0 INTO promena;
EXIT WHEN rc_table0%NOTFOUND;
dbms_output.put_line('test: '||promena);
END LOOP;
CLOSE rc_table0;
END;
/
As you're only expecting a single row back there isn't much point doing that; and if you expected multiple rows (from a modified query, e.g. getting several days data and grouping by day) then an implicit cursor would be simpler anyway:
BEGIN
FOR r IN (
SELECT DISTINCT listagg(ITEM_ID,', ') within group (order by ITEM_ID) AS promena
FROM TB_PL_M_WRKORD WRKOD
WHERE 1 = 1
AND WO_DATE = '20181012'
AND WRKOD.ITEM_ID NOT IN (SELECT ITEM_ID FROM TB_CM_M_FERT_COST_CHK FERT)
AND WC_ID = 'U'
)
LOOP
dbms_output.put_line('test: '||r.promena);
END LOOP;
END;
/
If this is really part of a procedure and the rc_table0 is an OUT parameter then you just can't do this. In code you posted as an answer you tried:
OPEN RC_TABLE0 FOR
SELECT listagg(ITEM_ID,', ') within group(order by ITEM_ID)
INTO promena
FROM TB_PL_M_WRKORD WRKOD
...
In that construct the into is still ignored, because the open doesn't fetch anything. And if you loop and fetch inside your procedure to display the results as I did above then you are consuming the result set, so the caller will get no results (or "ORA-01001: invalid cursor" if you close it inside the procedure).
You just can't do both, unless you re-open the cursor, which seems like overhead you probably don't want...

does LISTAGG in oracle handle number datatype

I have a result as shown below
I_KEY
10001
10002
10003
10004
10005
I need to modify the result to show output as
I_KEY
10001,10002,10003,10004,10005
I have written oracle function to return the aggregate result as shown below, My doubt is, will the LISTAGG handle value with NUMBER type?
create or replace
FUNCTION GET_I_KEY(
RETURN NUMBER
IS
TEST I_DETAILS.I_KEY%TYPE;
BEGIN
SELECT LISTAGG(I_KEY, ',') WITHIN GROUP (ORDER BY I_KEY)
INTO TEST
FROM I_DETAILS
RETURN TEST;
EXCEPTION
WHEN NO_DATA_FOUND THEN
TEST := NULL;
RETURN TEST;
END;
UPDATED FUNCTION
create or replace
FUNCTION GET_I_KEY(
IN I_DETAILS.I_NAME%TYPE)
RETURN VARCHAR2
IS
TEST VARCHAR2(4000);
BEGIN
SELECT LISTAGG(I_KEY, ',') WITHIN GROUP (ORDER BY I_KEY)
INTO TEST
FROM I_DETAILS ID ,I_TYPE IT
WHERE ID.I_KEY = IT.I_KEY
AND ID.I_NAME = IN;
RETURN TEST;
END;
From the documentation:
The arguments to the function are subject to the following rules:
The measure_expr can be any expression. Null values in the measure column are ignored.
...
The return data type is RAW if the measure column is RAW; otherwise the return value is VARCHAR2.
The measure_expr, which is your I_KEY column in this case, can be a number. (The numeric values will be implicitly converted to strings). The result cannot be a number - as you are not passing in RAW data, it will be a VARCHAR.
The function you've posted has syntax errors which you may have introduced while posting, but with those corrected will get a run-time error because you've declared TEST as a number (via the %TYPE syntax).
create or replace
FUNCTION GET_I_KEY
RETURN NUMBER
IS
TEST I_DETAILS.I_KEY%TYPE;
BEGIN
SELECT LISTAGG(I_KEY, ',') WITHIN GROUP (ORDER BY I_KEY)
INTO TEST
FROM I_DETAILS;
RETURN TEST;
EXCEPTION
WHEN NO_DATA_FOUND THEN
TEST := NULL;
RETURN TEST;
END;
/
Function GET_I_KEY compiled
select get_i_key from dual;
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at "MY_SCHEMA.GET_I_KEY", line 6
You need to declare bothTEST and the function return type as strings. You also don't need the exception handler - listagg() is an aggregate function so it will never return no rows; as there is no group-by clause it will always return exactly 1 row, which will be null if there is no data.
create or replace
FUNCTION GET_I_KEY
RETURN VARCHAR2
IS
TEST VARCHAR2(4000);
BEGIN
SELECT LISTAGG(I_KEY, ',') WITHIN GROUP (ORDER BY I_KEY)
INTO TEST
FROM I_DETAILS;
RETURN TEST;
END;
/
Function GET_I_KEY compiled
select get_i_key from dual;
GET_I_KEY
----------------------------------------
10001,10002,10003,10004,10005
If you have duplicate values in your raw data and you want to eliminate those from the result, you can just use a subquery; change the query inside your function to:
SELECT LISTAGG(I_KEY, ',') WITHIN GROUP (ORDER BY I_KEY)
INTO TEST
FROM (
SELECT DISTINCT I_KEY
FROM I_DETAILS
);
or with your modified function the same pattern:
create or replace
FUNCTION GET_I_KEY(
IN I_DETAILS.I_NAME%TYPE)
RETURN VARCHAR2
IS
TEST VARCHAR2(4000);
BEGIN
SELECT LISTAGG(I_KEY, ',') WITHIN GROUP (ORDER BY I_KEY)
INTO TEST
FROM (
SELECT DISTINCT IT.I_KEY
FROM I_DETAILS ID
JOIN I_TYPE IT
ON IT.I_KEY = ID.I_KEY
WHERE ID.I_NAME = IN
);
RETURN TEST;
END;
/
although the join seems pointless - you can just query I_DETAILS unless you have orphan key values which don't appear in I_TYPE and you're trying to exclude those.

Using Rownum in Cursor Bulk Collect Oracle

I'm trying to use the rownum to simulate a column autonumbered as I need to use it as an ID. Since it is an ID, I look at the final table if no record with MAX (ID).
The problem I have is when I want to do arithmetic operations within the cursor or when you invoke, or when you want to use a function. The ROWNUM (v_id) field is empty me when I want to print with DBMS_OUTPUT . Anyone have any idea how to solve it without using sequences ?
Here put the sample code.
declare
max_id number;
CURSOR INSRT(w_max number) IS
SELECT f_max_fact_sap(to_number(V_ID),w_max) AS V_ID,Seriei,serief
FROM (SELECT To_Char(ROWNUM) AS V_ID, A.*
FROM (SELECT DISTINCT a.matnr, a.seriei, a.serief,a.xblnr,a.fecha_sap, ((SERIEF-SERIEI)+1) AS rango
FROM SOPFUN.TT_ZMOVIMI_FACTURADAS a
WHERE 0 =(SELECT COUNT(1)
FROM PA_ZMOVIMI_FACTURADAS B
WHERE A.SERIEI = B.SERIEI
AND A.SERIEF = B.SERIEF
AND A.MATNR = B.MATNR
AND A.FECHA_SAP=B.FECHA_SAP)
AND A.FECHA_SAP IS NOT NULL) A);
TYPE T_INSRT IS TABLE OF INSRT%ROWTYPE INDEX BY PLS_INTEGER;
V_INSRT T_INSRT;
begin
SELECT Max(Nvl(ID,10000)) INTO MAX_ID-- To Proof because the table is empty
FROM PA_ZMOVIMI_FACTURADAS;
OPEN INSRT(MAX_ID);
LOOP
FETCH INSRT BULK COLLECT INTO V_INSRT LIMIT 1000;
FOR I IN 1 .. V_INSRT.Count loop
DBMS_OUTPUT.PUT_LINE('ID: ' ||V_INSRT(I).V_ID||' SI: '||V_INSRT(I).SERIEI||' SI: '||V_INSRT(I).SERIEF||' OPERACION: '||to_char(f_max_fact_sap(V_INSRT(I).V_ID,MAX_ID)));
end loop;
EXIT WHEN INSRT%NOTFOUND;
END LOOP;
end;

Resources