dbms_random exclude special characters - sql

I have this code, and it is working. But the issue is that I need to exclude the rest of the special characters like '!"#$%&()''*+,-/:;<=>?_'; and I need only one special character in the password. So I just need Uppercase and lowercase and one of the 3 special characters (#$&)
the result of this like \v:BX$5{
The result I need like
N5$aKtw6 Km&r2xF5 sZ8Q#thy fH57$ymc
create or replace function p_random_string return varchar2 is
pwd varchar2(8);
begin
loop
pwd:=dbms_random.string('p',8);
exit when regexp_instr(pwd,'[a-z]')>0 -- at least one lower case letter
and regexp_instr(pwd,'[A-Z]')>0 -- at least one upper case letter
and regexp_instr(pwd,'[1-9]')>0 -- at least one number
and (instr(pwd,'#')>0 or instr(pwd,'$')>0 or instr(pwd,'&')>0) -- at least one spcecial char in a list
and instr(pwd,'O')=0 -- not O (o)
and instr(pwd,'L')=0 -- not L (l)
and instr(pwd,'I')=0 -- not I (i)
and instr(pwd,0)=0 ; -- not 0 (zero)
end loop;
return pwd;
end;
/

A simple option is to translate offending characters to something else (e.g. into letters; see line #8):
SQL> CREATE OR REPLACE FUNCTION p_random_string
2 RETURN VARCHAR2
3 IS
4 pwd VARCHAR2 (8);
5 BEGIN
6 LOOP
7 pwd := DBMS_RANDOM.string ('p', 8);
8 pwd := TRANSLATE (pwd, '!"#$%&()''*+,-/:;<=>?_',
9 'abcdefghijklmnopqrstuv');
10 EXIT WHEN REGEXP_INSTR (pwd, '[a-z]') > 0 -- at least one lower case letter
11 AND REGEXP_INSTR (pwd, '[A-Z]') > 0 -- at least one upper case letter
12 AND REGEXP_INSTR (pwd, '[1-9]') > 0 -- at least one number
13 AND ( INSTR (pwd, '#') > 0
14 OR INSTR (pwd, '$') > 0
15 OR INSTR (pwd, '&') > 0) -- at least one spcecial char in a list
16 AND INSTR (pwd, 'O') = 0 -- not O (o)
17 AND INSTR (pwd, 'L') = 0 -- not L (l)
18 AND INSTR (pwd, 'I') = 0 -- not I (i)
19 AND INSTR (pwd, 0) = 0; -- not 0 (zero)
20 END LOOP;
21
22 RETURN pwd;
23 END;
24 /
Function created.
Testing:
SQL> SELECT p_random_string FROM DUAL;
P_RANDOM_STRING
--------------------------------------------------------------------------------
8H[KdY`#
SQL> SELECT p_random_string FROM DUAL;
P_RANDOM_STRING
--------------------------------------------------------------------------------
^#qPa6Q3
SQL> SELECT p_random_string FROM DUAL;
P_RANDOM_STRING
--------------------------------------------------------------------------------
sspV#q9E
SQL>
Though, looks like you'd want to remove some other "special" characters as well ...

Rather than using DBMS_RANDOM.STRING, you can work on a string with allowed characters and then loop eight times to get the eight random character of the list.
After producing such a string, check the counts and exit when all count conditions are met.
CREATE OR REPLACE FUNCTION p_random_string RETURN VARCHAR2 IS
v_allowed_chars VARCHAR2(100) := 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ123456789$&#';
v_pwd VARCHAR2(8 CHARS);
v_char CHAR;
v_rnd INTEGER;
BEGIN
LOOP
v_pwd := '';
FOR POS IN 1..8 LOOP
v_rnd := ROUND(DBMS_RANDOM.value(1, LENGTH(v_allowed_chars)));
v_char := SUBSTR(v_allowed_chars, v_rnd, 1);
v_pwd := v_pwd || v_char;
END LOOP;
EXIT WHEN REGEXP_COUNT (v_pwd, '[a-z]') > 0 -- at least one lower case letter
AND REGEXP_COUNT (v_pwd, '[A-Z]') > 0 -- at least one upper case letter
AND REGEXP_COUNT (v_pwd, '[1-9]') > 0 -- at least one digit
AND REGEXP_COUNT (v_pwd, '[\$#&]') = 1; -- exactly one spcecial char in a list
END LOOP;
RETURN v_pwd;
END p_random_string;
/

Related

How to iterate over binary string in Oracle?

enter image description here
declare
str varchar2(2000) := :inputstr;
v_len number;
currChar CHAR(1);
begin
v_len := length(str);
for i in 1..v_len
Loop
currChar := substr(str,i,1);
if currChar = 1 then
dbms_output.put_line('curr index' || i);
end if;
End loop;
end;
When I pass '000111000' as input to IN_STRING variable , it trims the string and behaves very unusually.Please suggest some good approaches to iterate over binary strings like this.I am expecting output as 4,5,6 from above operation.
EDIT1:
Please don't directly input the string as str varchar2(2000) := '000111000';
Instead input it from bind variable as I mentioned above.
Your code works so long as you pass in a VARCHAR2 data type (and not a NUMBER).
You can also tidy up the code passing in the bind variable only once and using CONSTANTs to hold the values that are constant:
VARIABLE in_string VARCHAR2;
DECLARE
c_string CONSTANT VARCHAR2(200) := :in_string;
c_length CONSTANT PLS_INTEGER := LENGTH(c_string);
v_out CHAR(1);
BEGIN
FOR i IN 1..c_length
LOOP
v_out := SUBSTR(c_string,i,1) ;
DBMS_OUTPUT.PUT_LINE(v_out);
END LOOP;
END;
/
Which outputs:
0
0
1
1
1
0
0
0
db<>fiddle here
Shouldn't behave unusual, unless datatype of in_string variable is NUMBER (then leading zeros don't have any meaning) - switch to VARCHAR2.
Illustration:
NUMBER variable datatype
value you enter
result - really, missing leading zeros
Otherwise, it works OK (this is SQL*Plus so I used substitution variable):
SQL> DECLARE
2 v_length NUMBER (10);
3 v_out VARCHAR2 (20);
4 BEGIN
5 v_length := LENGTH ( '&&in_string');
6
7 FOR i IN 1 .. v_length
8 LOOP
9 v_out := SUBSTR ( '&&in_string', i, 1);
10 DBMS_OUTPUT.PUT_LINE (v_out);
11 END LOOP;
12 END;
13 /
Enter value for in_string: 00111000
0
0
1
1
1
0
0
0
PL/SQL procedure successfully completed.
Another option (if you're interested in it) doesn't require PL/SQL:
SQL> SELECT SUBSTR ( '&&in_string', LEVEL, 1) val
2 FROM DUAL
3 CONNECT BY LEVEL <= LENGTH ( '&&in_string');
V
-
0
0
1
1
1
0
0
0
8 rows selected.
SQL>

Validate sequential number and characters in select regexp

I need to validate a serial number of chassis that It doesn't have sequential number or characters like this:
Serial number Validate
1234567890 true
aaaaaaaaaa true
aaabbbcccd true
123abc4567 true
22aabb3398 true
abcdefghij true
13er4t691d false
vc-376t65e false
But only different chars and numbers.
I've tried with some like this:
SELECT REGEXP_INSTR ('1088', '^(?!.*?([\w])\1).+') FROM dual;
But returns 0 in validation.
Here's a function I hacked up that would do this:
create or replace function f2 (mystring varchar2) return varchar2 is
strlen number;
curchar char(1);
nextchar char(1);
validate varchar2(5);
begin
select 'false' into validate from dual;
select length(mystring) - 1 into strlen from dual;
for loopindex in 1..strlen
loop
select substr(mystring, loopindex, 1) into curchar from dual;
if curchar between '0' and '9' or curchar between 'a' and 'z' or curchar between 'A' and 'Z' then
select substr(mystring, loopindex + 1, 1) into nextchar from dual;
if ascii(nextchar) - ascii(curchar) between 0 and 1 then
select 'true' into validate from dual;
end if;
end if;
end loop;
return validate;
end;
SQL> select f2('abcdefghij') c1, f2('vc-376t65e') c2 from dual;
C1 C2
---------- --------------------
true false
It works for all your examples above.
One caveat just because I noticed: The above assumes the following character is not sequential ascending. Your last example contains both '76' and '65' which are sequential but descending. If this is intended to allow this, great. If you also want to flag this type of example, change the if statement above to the following:
if abs(ascii(nextchar) - ascii(curchar)) between 0 and 1 then
This will return true if any characters are one more than, equal to, or one less than the previous character.

PL/SQL LOOP - Return a row with mixed capital letters

I know this question probably has an easy answer, but I can't get my head around it.
I'm trying to, inside a loop, return a string (in the SQL output) with mixed capital and non-capital letters.
Example: If a name in the row is John Doe, the output will print JoHn DoE, or MiXeD CaPiTaL.
This is my code (which I know is poor written but I need to use the cursor!):
declare
aa_ VARCHAR2(2000);
bb_ NUMBER:=0;
cc_ NUMBER:=0;
CURSOR cur_ IS
SELECT first_name namn, last_name efternamn FROM person_info
;
begin
FOR rec_ IN cur_ LOOP
dbms_output.put_line(rec_.namn);
FOR bb_ IN 1.. LENGTH(rec_.namn) LOOP
dbms_output.put(UPPER(SUBSTR(rec_.namn,bb_,1)));
cc_ := MOD(bb_,2);
IF cc_ = 0 THEN
dbms_output.put(UPPER(SUBSTR(rec_.namn,cc_,1)));
ELSE
dbms_output.put(LOWER(SUBSTR(rec_.namn,2)));
END IF;
end loop;
dbms_output.new_line;
end loop;
end;
Again, I know the code is really bad but yeah, trying to learn!
Thanks in advance :)
You may use plain SQL for this purpose, without any loop:
Split input text by pairs separated with some special character (that doesn't appear in the text).
Use initcap SQL function to turn each first letter to upper case.
Remove the special separator.
with a as (
select 'John Doe' as a
from dual
union all
select 'mixed capital and non-capital letters'
from dual
)
select
replace(
initcap(
/*Convert case*/
regexp_replace(a, '([a-zA-Z]{2})',
/*Add ASCII nul after each two letters*/
'\1' || chr(0)
)
),
/*Remove ASCII nul to revert changes*/
chr(0)
) as mixed_case
from a
| MIXED_CASE |
| :------------------------------------ |
| JoHn DoE |
| MiXeD CaPiTaL AnD NoN-CaPiTaL LeTtErS |
db<>fiddle here
I'd put the text transformation into a function, rather than including all the logic in the body of the loop.
declare
cursor c_people is
select 'John' as first_name, 'Doe' as last_name from dual union all
select 'Mixed', 'Capitals'
from dual;
function mixCaps(inText varchar2) return varchar2
is
letter varchar2(1);
outText varchar2(4000);
begin
for i in 1..length(inText) loop
letter := substr(inText,i,1);
outText := outText ||
case mod(i,2)
when 0 then lower(letter)
else upper(letter)
end;
end loop;
return outText;
end mixCaps;
begin
for person in c_people loop
dbms_output.put_line(mixCaps(person.first_name|| ' ' || person.last_name));
end loop;
end;
If performance was critical and you had large numbers of values, you might consider inlining the function using pragma inline (but then you wouldn't be using dbms_output anyway).
For learning purpose you can use code below (it is not efficient it is for learning of oracle features)
Steps :
split word on letters using connect by level
get Nth (level) occurence of one letter ('.?') from word using reg exp
convert to upper case every 2nd letter
concatenate back using list agg and sorting by letter number
used here function in with so you can apply it to any sql table
with
function mixed(iv_name varchar2) return varchar2 as
l_result varchar2(4000);
begin
with src_letters as
(select REGEXP_SUBSTR(iv_name, '.?', level) as letter
,level lvl
from dual
connect by level <= length(iv_name)),
mixed_letters as
(select case
when mod(lvl, 2) = 0 then
letter
else
upper(letter)
end as letter
,lvl
from src_letters
order by lvl)
select listagg(letter) within group(order by lvl)
into l_result
from mixed_letters;
return l_result;
end;
select mixed('text') from dual

Oracle SQL Developer Query on recommended password setup

Setting up a random password for user using
select
dbms_random.string('L',2) || dbms_random.string('X',6) || '1!' as deflvrpwd,
'${access_request_cri_acc_cas9}' as ACNTDN
from dual
New requirement
New Hire Details:
Name :John Doe
Region: America
WDID : 876214
WDID Reverse and split
Region in the middle with the letter A replaced with # symbol
Should read if we follow your formula.
= 412#meric#s678
Please suggest attribute are same as mentioned.
Thank You
Here's one option; read comments within code.
SQL> WITH
2 -- sample data
3 test (name, region, wdid)
4 AS
5 (SELECT 'John Doe', 'America', '876214' FROM DUAL),
6 temp
7 AS
8 -- reverse WDID; don't use undocumented REVERSE function
9 -- replace "A" (or "a") with "#" in REGION
10 ( SELECT name,
11 REPLACE (REPLACE (region, 'A', '#'), 'a', '#') new_region,
12 LISTAGG (letter, '') WITHIN GROUP (ORDER BY lvl DESC) new_wdid
13 FROM ( SELECT SUBSTR (wdid, LEVEL, 1) letter,
14 LEVEL lvl,
15 name,
16 region
17 FROM test
18 CONNECT BY LEVEL <= LENGTH (wdid))
19 GROUP BY name, region)
20 -- finally
21 SELECT SUBSTR (new_wdid, 1, 3) || new_region || SUBSTR (new_wdid, 4) AS result
22 FROM temp;
RESULT
--------------------------------------------------------------------------------
412#meric#678
SQL>
I don't know where s in your result comes from (this: 412#meric#s678).
There's a small cost in context switching between SQL and PL/SQL, but this doesn't sound like a high-volume or performance-critical thing, so you might find it cleaner to put the logic in a function:
create or replace function get_password (p_wdid varchar2, p_region varchar2)
return varchar2 as
l_split pls_integer;
l_password varchar2(30);
begin
-- split WDID halfway, but allow for odd lengths
l_split := floor(length(p_wdid)/2);
-- iterate over the WDID in reverse
for i in reverse 1..length(p_wdid) LOOP
-- when we reach the split point, append the modified region
if i = l_split then
l_password := l_password || translate(p_region, 'Aax', '##x');
end if;
-- append each WDID character, in reverse order
l_password := l_password || substr(p_wdid, i, 1);
end loop;
return l_password;
end get_password;
/
The WDID is reversed in a loop, and the modified region is included at the midway point, based on the length of the WDID value.
You can then do:
select get_password('876214', 'America') from dual;
GET_PASSWORD('876214','AMERICA')
--------------------------------
412#meric#678
This also doesn't have the unexplained 's' from the example in your question.
If you can't create a function but are on a recent version of Oracle then you can define an ad hoc function in a CTE:
with
function invert (p_input varchar2) return varchar2 as
l_output varchar2(30);
begin
for i in reverse 1..length(p_input) LOOP
l_output := l_output || substr(p_input, i, 1);
end loop;
return l_output;
end invert;
t (wdid, region) as (
select invert('876214'), translate('America', 'Aax', '##x')
from dual
)
select substr(wdid, 1, floor(length(wdid)/2))
|| region
|| substr(wdid, floor(length(wdid)/2) + 1)
from t;
which gets the same result. (I've called the function invert to avoid confusion with the undocumented reverse function.)
db<>fiddle showing both.

Oracle. Not valid ascii value of regex result

I'd like to edit a string. Get from 2 standing nearby digits digit and letter (00 -> 0a, 01 - 0b, 23-> 2c etc.)
111324 -> 1b1d2e.
Then my code:
set serveroutput on size unlimited
declare
str varchar2(128);
function convr(num varchar2) return varchar2 is
begin
return chr(ascii(num)+49);
-- return chr(ascii(num)+49)||'<-'||(ascii(num)+49)||','||ascii(num)||','||num||'|';
end;
function replace_dd(str varchar2) return varchar2 is
begin
return regexp_replace(str,'((\d)(\d))','\2'||convr('\3'));
end;
begin
str := '111324';
Dbms_Output.Put_Line(str);
Dbms_Output.Put_Line(replace_dd(str));
end;
But I get the next string: '112'.
When I checked result by commented return string I'v got:
'1<-141,92,1|1<-141,92,3|2<-141,92,4|'.
ascii(num) does not depend on num. It always works like ascii('\'). It is 92, plus 49 we got 141 and it is out of ascii table. But num by itself is printed correctly.
How can I get correct values? Or maybe another way to resolve this issue?
What is happening is that the replacement string is expanded first, and only after it is fully processed, any remaining backreferences like \2 are replaced by string fragments. So convr('\3') is processed first, and at this stage '\3' is a literal. ascii() returns the ascii code of the FIRST character of whatever string it receives as argument. So the 3 plays no role, you only get ascii('\') as you noticed. Then your user-defined function is evaluated and plugged back into the concatenation... by now there is no \3 left in the replacement string.
Exercise: Try to explain/understand why
regexp_replace('abcdef', '(b).*(e)', '\2' || upper('\1'))
is aebf and not aeBf. (Hint: what is the return from upper('\1') by itself, unrelated to anything else?)
You could split the input string into component characters, apply your transformation on those with even index and combine the string back (all in SQL, no need for loops and such). Something like this (done in plain SQL, you can rewrite it into your function if you like):
with
inputs ( str ) as (
select '111324' from dual union all
select '372' from dual
),
singletons ( str, idx, ch ) as (
select str, level, substr(str, level, 1)
from inputs
connect by level <= length(str)
and prior str = str
and prior sys_guid() is not null
)
select str,
listagg(case mod(idx, 2) when 1 then ch else chr(ascii(ch)+49) end, '')
within group (order by idx)
as modified_str
from singletons
group by str
;
STR MODIFIED_STR
------ --------------
111324 1b1d2e
372 3h2
Here code adds 5 to a single letter and resolve the isssue.
set serveroutput on size unlimited
declare
str varchar2(128);
str1 varchar2(128);
function replace_a(str varchar2) return varchar2 is
begin
return regexp_replace(str,'(\D)','5\1');
end;
function convr(str varchar2) return varchar2 is
ind number;
ret varchar2(128);
begin
Dbms_Output.Put_Line(str);
--return chr(ascii(num)+49)||'<-'||(ascii(num)+49)||','||ascii(num)||','||num||'|';
ind := 1 ;
ret :=str;
loop
ind := regexp_instr(':'||ret,'(#\d#)',ind) ;
exit when ind=0;
Dbms_Output.Put_Line(ind);
ret := substr(ret,1,ind-2)||chr(ascii(substr(ret,ind,1))+49)||substr(ret,ind+2);
SYS.Dbms_Output.Put_Line(ret);
end loop;
return ret;
end;
function replace_dd(str varchar2) return varchar2 is
begin
return convr(regexp_replace(str,'((\d)(\d))','\2#\3#'));
end;
begin
str := '11a34';
Dbms_Output.Put_Line(str);
Dbms_Output.Put_Line(replace_a(str));
Dbms_Output.Put_Line(replace_dd(replace_a(str)));
end;
result:
11a34
115a34
1#1#5a3#4#
3
1b5a3#4#
7
1b5a3e
1b5a3e