How to iterate over binary string in Oracle? - sql

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>

Related

dbms_random exclude special characters

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;
/

PL/SQL CREATE PROCEDURE - factorial

I don't know what's wrong with my code block to create a procedure to find out the factorial of a number. Thank you.
Question 1: write a stored procedure that gets an integer number n and calculates and displays its factorial.
SET SERVEROUTPUT ON;
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER) AS
factorial NUMBER;
num Number;
BEGIN
FOR i IN REVERSE 1..n LOOP
num := i - 1;
factorial := factorial * num;
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
/
BEGIN
factorial_number(5);
END;
/
You're failing to initialize the local variable factorial. Uninitialized variables are initially null and multiplying null by any value produces null.
I don't see why you'd want your loop to go in reverse order. It doesn't matter since multiplication is communitive but it it unlikely to make your code easier to read/ debug/ follow.
You don't want to subtract 1 from the value you are multiplying by on each iteration of the loop. When i = 1, for example, you're multiplying factorial by 0 which means that (assuming you initialize factorial), you'd always end up with 0. You want to multiply by i so there is no need for the local variable num.
If I make those fixes
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER)
AS
factorial NUMBER := 1; -- Initialize
BEGIN
FOR i IN 1..n LOOP -- Loop normally
dbms_output.put_line( 'Beginning iteration ' || i || ' of loop. ' ||
'Factorial = ' || factorial ||
' multiplying by ' || i );
factorial := factorial * i; -- Multiply by i not i-1
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
Then
BEGIN
factorial_number(5);
END;
will print out 120 (5*4*3*2*1).
I'm also adding an additional dbms_output line to print out the current state of the variables on each iteration of the loop. That's a relatively old-school method of debugging. In the modern world, you'd walk through the code with a debugger where you can see the values of your local variables but introductory classes may not teach debugger usage initially.
How about
SQL> CREATE OR REPLACE PROCEDURE factorial_number (n NUMBER)
2 AS
3 factorial NUMBER := 1;
4 BEGIN
5 FOR i IN 1 .. n
6 LOOP
7 factorial := factorial * i;
8 END LOOP;
9
10 DBMS_OUTPUT.PUT_LINE (factorial);
11 END;
12 /
Procedure created.
SQL>
SQL> BEGIN
2 factorial_number (5);
3 END;
4 /
120
PL/SQL procedure successfully completed.
SQL>

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

pl/sql even and odd sum block

I have a pl/sql programming question: For numbers between 1..50, you need to multiply even numbers by five, odd numbers by 3 and then find sum of all the numbers in the result.
So I had this so far
DECLARE
ln_num NUMBER :=0;
ln_num1 NUMBER :=0;
ln_num2 NUMBER :=0;
BEGIN
for i in 1..50 loop
if mod(i,2) =0 then
ln_num:=i*5;
elsif mod(i,2) = 1 then
ln_num1:=i*3;
ln_num2 := ln_num+ln_num1;
dbms_output.put_line(ln_num2);
end if;
end loop;
END;
This gives me a last list of numbers but i need the sum of all of them. I was wondering what I was missing and how do I fix this?
Thanks
create or replace function odd_even_Numbers(num number)
return varchar2 is
value1 number;
message varchar2(30);
a number;
begin
a:=num;
loop
select abs(REMAINDER(a, 2)) into value1 from dual;
if (value1=1) then
message:= 'odd number';
dbms_output.put_line (a||'-----'||message);
a:=a+1;
else
message:='even number';
dbms_output.put_line (a||'-----'||message);
a:=a+1;
end if;
exit when a=20;
end loop;
message:='done well';
return message;
end;
SQL> DECLARE
2 ln_num NUMBER :=0;
3 ln_num1 NUMBER :=0;
4 ln_num2 NUMBER :=0;
5
6 BEGIN
7 for i in 1..50 loop
8
9 if mod(i,2) =0 then
10 ln_num:=ln_num+i*5; -- changes
11
12 elsif mod(i,2) = 1 then
13 ln_num1:=ln_num1+i*3; -- changes
14
15
16 end if;
17 end loop;
18 ln_num2 := ln_num+ln_num1;
19 dbms_output.put_line(' the result is ' || ln_num2);
20
21 END;
22
23
24 /
the result is 5125
PL/SQL procedure successfully completed.

oracle pl/sql ora-01722 error

I have a simple oracle statement in my procedure:
update org.security_training_question a
set a.actv_indr = 'N' where a.qstn_id in (v_qstns_to_delete);
v_qstns_to_delete is a parameter being passed. It is a varchar2 field and a.qstn_id is a numeric field.
When calling the Stored Procedure, for v_qstns_to_delete I am passing the following String: "24, 43, 23, 44, 21".
When I run the statement output the stored procedure thenn it runs fine but when I run it as a stored procedure I get an error on the above line saying Invalid Number.
Any clue?
You can't use a "in" clause with a variable like that. One way around it is
declare stmt varchar2(4000);
begin
stmt := 'update org.security_training_question a set a.actv_indr = ''N'' where a.qstn_id in ('||v_qstns_to_delete||')';
execute immediate stmt;
end;
if v_qstns_to_delete is a varchar, you would need to convert it somewhat to let Oracle understand that there may be several items in it. One method would be to convert the string to a table of items.
Supposing qstn_id is a NUMBER column, you would:
SQL> CREATE TYPE tab_number AS TABLE OF NUMBER;
2 /
Type created
SQL> CREATE OR REPLACE FUNCTION to_tab_number(p_in VARCHAR2,
2 p_separator VARCHAR2 DEFAULT ',')
3 RETURN tab_number AS
4 l_result tab_number := tab_number();
5 l_tail LONG := p_in;
6 BEGIN
7 WHILE l_tail IS NOT NULL LOOP
8 l_result.EXTEND;
9 IF instr(l_tail, p_separator) != 0 THEN
10 l_result(l_result.COUNT) := to_number(substr(l_tail,
11 1,
12 instr(l_tail, p_separator) - 1));
13 l_tail := substr(l_tail, instr(l_tail, p_separator) + 1);
14 ELSE
15 l_result(l_result.COUNT) := to_number(l_tail);
16 l_tail := NULL;
17 END IF;
18 END LOOP;
19 RETURN l_result;
20 END;
21 /
Function created
You could then convert a string to a table of number from SQL:
SQL> SELECT * FROM TABLE(to_tab_number('24, 43, 23, 44, 21'));
COLUMN_VALUE
------------
24
43
23
44
21
To do a variable in-list:
SQL> SELECT object_id, owner
2 FROM all_objects
3 WHERE object_id IN (SELECT column_value FROM TABLE(to_tab_number('18,19,20')));
OBJECT_ID OWNER
---------- ------------------------------
18 SYS
19 SYS
20 SYS
More on the same subject on askTom.