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
Related
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;
I have created some kind of code block till now, and I'm stuck. I'm selecting from table called FND_ORACLE_USERID for EBS database user schemas. Idea is to have those accounts printed in format like this (password is placeholder):
FNDCPASS user/password 0 Y system/test11 ALLORACLE password
Till now I have done this:
set serveroutput on;
DECLARE
c1 SYS_REFCURSOR;
l_pass varchar(16);
l_count number(3);
BEGIN
select count (*) into l_count from FND_ORACLE_USERID
where READ_ONLY_FLAG='A';
OPEN c1 for
select xmlagg(xmlelement("r", ch)).extract('//text()').getstringval() ch
from
(
select distinct first_value(ch) over (partition by lower(ch)) as ch
from (
select substr('abcd$efghijklmn#pqrstuvw#xyzABC$DEFGHIJK$LMNPQR!STUVWXYZ1!23456789',
level, 1) as ch
from dual
connect by level <= 59
order by dbms_random.value
)
where rownum <= dbms_random.value(17,17)
);
LOOP
FETCH c1
INTO l_pass;
EXIT WHEN c1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('FNDCPASS user/'||l_pass||' 0 Y system/test11 ALLORACLE '||l_pass||'');
END LOOP;
CLOSE c1;
END;
/
So I have started to count schemas, and I would like to stop printing new lines from above, when it reach end of count variable.
I'm not so good with plsql so please could you give some advice?
Thanks
Ok, i have created password function which returns string value for my password generator code now, like this :
create or replace function generic_passwd
return varchar2 is passwd VARCHAR2(17);
begin
select xmlagg(xmlelement("r", ch)).extract('//text()').getstringval() ch into passwd
from
(
select distinct first_value(ch) over (partition by lower(ch)) as ch
from (
select substr('abcd$efghijklmn#pqrstuvw#xyzABC$DEFGHIJK$LMNPQR!STUVWXYZ1!23456789',
level, 1) as ch
from dual
connect by level <= 59
order by dbms_random.value
)
where rownum <= dbms_random.value(17,17)
);
return passwd;
end;
/
Then i called this function from my code block and it is fine to me, like this:
set serveroutput on size 1000000
DECLARE
c1 SYS_REFCURSOR;
l_user varchar(10);
l_passd varchar(17);
BEGIN
OPEN c1 for
select oracle_username, generic_passwd() from FND_ORACLE_USERID where READ_ONLY_FLAG='A';
LOOP
FETCH c1
INTO l_user,l_passd;
EXIT WHEN c1%NOTFOUND ;
DBMS_OUTPUT.PUT_LINE('FNDCPASS '||l_user||'/'||l_passd||' 0 Y system/oracle11 ALLORACLE '||l_passd||'');
END LOOP;
CLOSE c1;
END;
/
Thank you for your time Jeff.
Found your own solution. Good on you. There are perhaps a couple inconsistencies, which may actually be intentional. But I think at least one or two are not:
The digits 3-9 can never appear in the output. You sub string
individual characters up to the 59th in the base string, however
that string is 66 characters in length. Perhaps the original base
string changed but the hard coded value did not. Intentional?
The output cannot have a repeated character, seems intentional, but
neither can it have a letter and the corresponding alternate case of
that letter. I.E cannot have both "a" and "A" in the output. Intentional?
The Expression: dbms_random.value(17,17) will always return 17, at least in a piratical since as rownum is always as integer.
So there is no reason to pay the function call overhead. Just use
the equivalent rownum <= 17;
Just a side note: This seems to be a limited use function. If that
is not the case then creating a standalone function is the way to
go. If, however this limited and it is version 12.1 or higher then
you may want to build the function directly into query using WITH
FUNCTION .... Doing so eliminate context switch between the
PL/SQL and SQL thus gaining performance.
The following addresses all these: See Demo But i reiterate. Except for #1 and #2 above what you have will work well.
with function generic_passwd --<<< define function directly as part of the query
return varchar2
is
l_generic_password varchar2(17);
begin
with base (stg) as
( select 'abcd$efghijklmn#pqrstuvw#xyzABC$DEFGHIJK$LMNPQR!STUVWXYZ1!23456789' from dual)
select xmlagg(xmlelement("r", ch)).extract('//text()').getstringval()
into l_generic_password
from
(
select distinct ch as ch --<< do not allow duplicate letters, but allow lower and upper of same letter
from (
select substr(stg,level, 1) as ch
from base
connect by level <= length(stg) --<< determine stop point from base string itself.
order by dbms_random.value
)
where rownum <= 17 --<< avoid subroutine overhead that always returns same value.
);
return l_generic_password;
end;
select username, generic_passwd() password
from fnd_oracle_userid;
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;
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...
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;
/