Validate sequential number and characters in select regexp - sql

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.

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

Need help for dynamic calculation in oracle sql query

I Have a table tab_1 with below values.
ID Calculation value
1 10
2 10
3 1+2
4 5
5 3-2
6 5+1
Need help writing the query for following logic. I have a table where the records contain either calculation strings or values to be used in calculations. I need to parse the calculation like this:
ID 3 is the sum of ID 1 and 2.
ID 5 is the minus of ID 3 and 2.
ID 6 is the sum of ID 5 and 1.
Then I need to select the records for the referenced IDs and perform the calculations. My
expected output:
ID Calculation value
3 1+2 20
5 3-2 10
6 5+1 20
Thanks -- nani
"Need help writing the query for below logic."
This is not a problem which can be solved in pure SQL, because:
executing the calculation string requires dynamic SQL
you need recursion to look up records and evaluate the results
Here is a recursive function which produces the answers you expect. It has three private procs so that the main body of the function is simple to understand. In pseudo-code:
look up record
if record is value then return it and exit
else explode calculation
recurse 1, 3, 4 for each part of exploded calculation until 2
Apologies for the need to scroll:
create or replace function dyn_calc
(p_id in number)
return number
is
result number;
n1 number;
n2 number;
l_rec t23%rowtype;
l_val number;
type split_calc_r is record (
val1 number
, operator varchar2(1)
, val2 number
);
l_calc_rec split_calc_r;
function get_rec
(p_id in number)
return t23%rowtype
is
rv t23%rowtype;
begin
select *
into rv
from t23
where id = p_id;
return rv;
end get_rec;
procedure split_calc
(p_calc in varchar2
, p_n1 out number
, p_n2 out number
, p_operator out varchar2)
is
begin
p_n1 := regexp_substr(p_calc, '[0-9]+', 1, 1);
p_n2 := regexp_substr(p_calc, '[0-9]+', 1, 2);
p_operator := translate(p_calc, '-+*%01923456789','-+*%'); --regexp_substr(p_calc, '[\-\+\*\%]', 1, 1);
end split_calc;
function exec_calc
(p_n1 in number
, p_n2 in number
, p_operator in varchar2)
return number
is
rv number;
begin
execute immediate
'select :n1 ' || p_operator || ' :n2 from dual'
into rv
using p_n1, p_n2;
return rv;
end exec_calc;
begin
l_rec := get_rec(p_id);
if l_rec.value is not null then
result := l_rec.value;
else
split_calc(l_rec.calculation
, l_calc_rec.val1
, l_calc_rec.val2
, l_calc_rec.operator);
n1 := dyn_calc (l_calc_rec.val1);
n2 := dyn_calc (l_calc_rec.val2);
result := exec_calc(n1, n2, l_calc_rec.operator);
end if;
return result;
end;
/
Run like this:
SQL> select dyn_calc(6) from dual;
DYN_CALC(6)
-----------
20
SQL>
or, to get the output exactly as you require:
select id, calculation, dyn_calc(id) as value
from t23
where calculation is not null;
Notes
There is no exception handling. If the data is invalid the function will just blow up
the split_calc() proc uses translate() to extract the operator rather than regex. This is because regexp_substr(p_calc, '[\-\+\*\%]', 1, 1) mysteriously swallows the -. This appears to be an environment-related bug. Consequently extending this function to process 1+4+2 will be awkward.
Here is a LiveSQL demo.
In SQL:
select 'ID ' +ID+ ' is the ' + case when calculation like '%-%' then ' minus '
when calculation like '%+%' then ' sum ' END +' of
ID'+replace(replace(calculation,'+',' and '),'-',' and ')
from tab_1
where calculation is not null
In Oracle:
select 'ID ' ||ID|| ' is the ' || case when calculation like '%-%' then ' minus '
when calculation like '%+%' then ' sum ' END|| ' of
ID'||replace(replace(calculation,'+',' and '),'-',' and ')
from tab_1
where calculation is not null

I have a string value i need to check the format of the value in plsql

I have a INPUT VARIABLE as string 'AB1234567' it should not be more than 9 digits. i need a function in oracle Using regular expressions i need to check the format of the string.
i.e The first two characters of the string should be alphabetes and the next 7 characters should be numbers.
If i get any other special characters in the first two characters of string the function need's to return 'F' and next 7 characters should be numbers if i get any junk characters in the next 7 variables then it needs to return 'f'.
The universal format of the string is 'AB1234567' first two characters are alpha and the next 7 should be the numbers .
Thank you
You can use regexp_like with different character classes to check for different patterns.
create or replace function str_test(txt in varchar2) return varchar2 as
begin
if not regexp_like(txt, '^[[:alpha:]]{2}') then
return 'F';
elsif not regexp_like(txt, '^.{2}\d{7}$') then
return 'f';
else
return 'some other output';
end if;
end;
/
If I correctly understand, you need something like this:
(upss, I missed not be more than 9 digits part, updated)
create function func(var nvarchar2)
return nvarchar2
as
begin
if NOT REGEXP_LIKE(var, '^[A-Z]{2}') then
return 'F';
elsif NOT REGEXP_LIKE(var, '^..[0-9]{7}$') then
return 'f';
else
return 'ok';
end if;
end;
CREATE OR REPLACE FUNCTION str_test (
str IN VARCHAR2
) RETURN CHAR
AS
BEGIN
IF str IS NULL THEN
RETURN 'X';
ELSE IF LENGTH( str ) != 9 THEN
RETURN 'Y'
ELSE IF SUBSTR( str, 1, 1 ) NOT BETWEEN 'A' AND 'Z'
OR SUBSTR( str, 2, 1 ) NOT BETWEEN 'A' AND 'Z' THEN
RETURN 'F';
ELSE
TO_NUMBER( SUBSTR( str, 3 ) );
RETURN NULL;
END IF;
EXCEPTION
WHEN other THEN
RETURN 'f';
END;
/

How do I expand a string with wildcards in PL/SQL using string functions

I have a column, which stores a 4 character long string with 4 or less wild characters (for eg. ????, ??01', 0??1 etc). For each such string like 0??1 I have to insert into another table values 0001 to 0991; for the string ??01, values will be be 0001 to 9901; for string ???? values will be 0000 to 9999 and so on.
How could I accomplish this using PL/SQL and string functions?
EDIT
The current code is:
declare
v_rule varchar2(50) := '????52132';
v_cc varchar2(50);
v_nat varchar2(50);
v_wild number;
n number;
begin
v_cc := substr(v_rule,1,4);
v_nat := substr(v_rule,5);
dbms_output.put_line (v_cc || ' '|| v_nat);
if instr(v_cc, '????') <> 0 then
v_wild := 4;
end if;
n := power(10,v_wild);
for i in 0 .. n - 1 loop
dbms_output.put_line(substr(lpad(to_char(i),v_wild,'0' ),0,4));
end loop;
end;
/
Would something like the following help?
BEGIN
FOR source_row IN (SELECT rule FROM some_table)
LOOP
INSERT INTO some_other_table (rule_match)
WITH numbers AS (SELECT LPAD(LEVEL - 1, 4, '0') AS num FROM DUAL CONNECT BY LEVEL <= 10000)
SELECT num FROM numbers WHERE num LIKE REPLACE(source_row.rule, '?', '_');
END LOOP;
END;
/
This assumes you have a table called some_table with a column rule, which contains text such as ??01, 0??1 and ????. It inserts into some_other_table all numbers from 0000 to 9999 that match these wild-carded patterns.
The subquery
SELECT LPAD(LEVEL - 1, 4, '0') AS num FROM DUAL CONNECT BY LEVEL <= 10000)
generates all numbers in the range 0000 to 9999. We then filter out from this list of numbers any that match this pattern, using LIKE. Note that _ is the single-character wildcard when using LIKE, not ?.
I set this up with the following data:
CREATE TABLE some_table (rule VARCHAR2(4));
INSERT INTO some_table (rule) VALUES ('??01');
INSERT INTO some_table (rule) VALUES ('0??1');
INSERT INTO some_table (rule) VALUES ('????');
COMMIT;
CREATE TABLE some_other_table (rule_match VARCHAR2(4));
After running the above PL/SQL block, the table some_other_table had 10200 rows in it, all the numbers that matched all three of the patterns given.
Replace * to %, ? to _ and use LIKE clause with resulting values.
To expand on #Oleg Dok's answer, which uses the little known fact that an underscore means the same as % but only for a single character and using PL\SQL I think the following is the simplest way to do it. A good description of how to use connect by is here.
declare
cursor c_min_max( Crule varchar2 ) is
select to_number(min(numb)) as min_n, to_number(max(numb)) as max_n
from ( select '0000' as numb
from dual
union
select lpad(level, 4, '0') as numb
from dual
connect by level <= 9999 )
where to_char(numb) like replace(Crule, '?', '_');
t_mm c_min_max%rowtype;
l_rule varchar2(4) := '?091';
begin
open c_min_max(l_rule);
fetch c_min_max
into t_mm;
close c_min_max;
for i in t_mm.min_n .. t_mm.max_n loop
dbms_output.put_line(lpad(i, 4, '0'));
end loop;
end;
/

How can I determine if a string is numeric in SQL?

In a SQL query on Oracle 10g, I need to determine whether a string is numeric or not. How can I do this?
You can use REGEXP_LIKE:
SELECT 1 FROM DUAL
WHERE REGEXP_LIKE('23.9', '^\d+(\.\d+)?$', '')
You ca try this:
SELECT LENGTH(TRIM(TRANSLATE(string1, ' +-.0123456789', ' '))) FROM DUAL
where string1 is what you're evaluating. It will return null if numeric. Look here for further clarification
I don't have access to a 10G instance for testing, but this works in 9i:
CREATE OR REPLACE FUNCTION is_numeric (p_val VARCHAR2)
RETURN NUMBER
IS
v_val NUMBER;
BEGIN
BEGIN
IF p_val IS NULL OR TRIM (p_val) = ''
THEN
RETURN 0;
END IF;
SELECT TO_NUMBER (p_val)
INTO v_val
FROM DUAL;
RETURN 1;
EXCEPTION
WHEN OTHERS
THEN
RETURN 0;
END;
END;
SELECT is_numeric ('333.5') is_numeric
FROM DUAL;
I have assumed you want nulls/empties treated as FALSE.
As pointed out by Tom Kyte in http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:7466996200346537833, if you're using the built-in TO_NUMBER in a user defined function, you may need a bit of extra trickery to make it work.
FUNCTION is_number(x IN VARCHAR2)
RETURN NUMBER
IS
PROCEDURE check_number (y IN NUMBER)
IS
BEGIN
NULL;
END;
BEGIN
PRAGMA INLINE(check_number, 'No');
check_number(TO_NUMBER(x);
RETURN 1;
EXCEPTION
WHEN INVALID_NUMBER
THEN RETURN 0;
END is_number;
The problem is that the optimizing compiler may recognize that the result of the TO_NUMBER is not used anywhere and optimize it away.
Says Tom (his example was about dates rather then numbers):
the disabling of function inlining will make it do the call to
check_date HAS to be made as a function call - making it so that the
DATE has to be pushed onto the call stack. There is no chance for the
optimizing compiler to remove the call to to_date in this case. If the
call to to_date needed for the call to check_date fails for any
reason, we know that the string input was not convertible by that date
format.
Here is a method to determine numeric that can be part of a simple query, without creating a function. Accounts for embedded spaces, +- not the first character, or a second decimal point.
var v_test varchar2(20);
EXEC :v_test := ' -24.9 ';
select
(case when trim(:v_test) is null then 'N' ELSE -- only banks, or null
(case when instr(trim(:v_test),'+',2,1) > 0 then 'N' ELSE -- + sign not first char
(case when instr(trim(:v_test),'-',2,1) > 0 then 'N' ELSE -- - sign not first char
(case when instr(trim(:v_test),' ',1,1) > 0 then 'N' ELSE -- internal spaces
(case when instr(trim(:v_test),'.',1,2) > 0 then 'N' ELSE -- second decimal point
(case when LENGTH(TRIM(TRANSLATE(:v_test, ' +-.0123456789',' '))) is not null then 'N' ELSE -- only valid numeric charcters.
'Y'
END)END)END)END)END)END) as is_numeric
from dual;
I found that the solution
LENGTH(TRIM(TRANSLATE(string1, ' +-.0123456789', ' '))) is null
allows embedded blanks ... it accepts "123 45 6789" which for my purpose is not a number.
Another level of trim/translate corrects this. The following will detect a string field containing consecutive digits with leading or trailing blanks such that to_number(trim(string1)) will not fail
LENGTH(TRIM(TRANSLATE(translate(trim(string1),' ','X'), '0123456789', ' '))) is null
For integers you can use the below. The first translate changes spaces to be a character and the second changes numbers to be spaces. The Trim will then return null if only numbers exist.
TRIM(TRANSLATE(TRANSLATE(TRIM('1 2 3d 4'), ' ','#'),'0123456789',' ')) is null