License check with REGEXP in PL-SQL Oracle Apex - sql

create or replace trigger "KENTEKEN_CHECK"
after insert or update of kenteken
on auto
for each row
declare
kenteken varchar2;
teller number := 0;
tellerletter number := 0;
tellercijfer number := 0;
begin
kenteken := lower(:NEW.kenteken);
loop
if substr(kenteken, teller, 1) = REGEXP ("[eoiau]") then
raise_application_error (-20502, 'Kenteken kan geen klinkers bevatten.');
elsif substr(kenteken, teller, 1) = REGEXP ("[0987654321]") then
tellercijfer := tellercijfer + 1;
elsif substr(kenteken, teller, 1) = REGEXP ("[qwrtypsdfghjklzxcvbnm]") then
tellerletter := tellerletter + 1;
else raise_application_error (-20502, 'Er is een ongeldig kenteken ingevoerd.');
end if;
teller := teller + 1;
exit when teller = 5;
end loop;
end;
I need to check a license plate (has six characters). It needs at least 2 letters and 2 numbers and the e, a, o, u and i are not allowed. How should I use the REGEXP the right way to check this?
Teller stands for counter
Tellernumber stands for a counter for the numbers
Tellerletter stands for a counter for the letters
Yes, I'm a starter with this so don't blame my style of coding... Never worked with REGEXP before so don't know how to use it.
Hope it's clear.

This could be an approach:
with test(s) as (
select 'axx11a' from dual union all
select 'xxxx1x' from dual union all
select '111111' from dual union all
select 'xx1111' from dual union all
select 'x1x1x1' from dual
)
select s
from test
where regexp_count(s, '[aeiou]') = 0
and regexp_count(s, '[qwrtypsdfghjklzxcvbnm]') >= 2
and regexp_count(s, '[0-9]') >= 2
The regexp_count counts the number of occurrences of the regular expression in the string; you may use it both to check that you have at least 2 characters of a given set and to check that you do not have unwanted characters.
You can rewrite this in different ways, I used the regexp_count to check all the cases to make it clearer.

You can do this with standard string functions (instead of regexp), which should result in improved performance - if that is a consideration.
To test whether a character is present, you can use TRANSLATE to remove that character from the string, and then compare the length of the resulting string to the length of the original. You need a small trick - TRANSLATE will remove (delete) the characters from the FROM list that do not have a correspondent in the TO list, but you can't have an empty TO list (if you do, the result will be the NULL string).
So, something like this:
with test( s ) as (
select 'axx11a' from dual union all
select 'xxxx1x' from dual union all
select '111111' from dual union all
select 'xx1111' from dual union all
select 'x1x1x1' from dual
)
-- end of test data; solution (SQL query) begins below this line
select s
from test
where length(translate(s, '~aeiou' , '~')) = length(s)
and length(translate(s, '~0123456789' , '~')) <= length(s) - 2
and length(translate(s, '~qwrtypsdfghjklzxcvbnm', '~')) <= length(s) - 2
;
S
------
xx1111
x1x1x1
In your procedure you can use these tests separately, in your IF... clauses.

It's important to realize that Oracle supports POSIX Extended Regular Expressions, which is more restricted than PCRE Regexes that you find most places.
Because POSIX ERE does not support so-called lookaheads, I would use these regexes:
.*[A-Z].*[A-Z]
To check if there are two letters (anywhere in the license plate).
.*[0-9].*[0-9]
To check if there are two numbers.
^[A-Z0-9]{6}$
To check if there are vowels I would use NOT REGEX_LIKE() with this regex:
[AEIOU]
To check whether the license plate consists of 6 numbers or letters from the start (^) to the end ($) of the string. This will fail if there are any other characters, or a different number of characters other than exactly 6.

([qwrtypsdfghjklzxcvbnm]{2}\d{2}) ==> dd02
(\d{2}[qwrtypsdfghjklzxcvbnm]{2}) ==> 02dd
([qwrtypsdfghjklzxcvbnm]{2}-\d{2}) ==> dd-02
I think that this will do your job.
Succes!!
Groet

Related

Can we sort the characters of a string in SQL oracle? [duplicate]

I'm looking for a function that would sort chars in varchar2 alphabetically.
Is there something built-in into oracle that I can use or I need to create custom in PL/SQL ?
From an answer at http://forums.oracle.com/forums/thread.jspa?messageID=1791550 this might work, but don't have 10g to test on...
SELECT MIN(permutations)
FROM (SELECT REPLACE (SYS_CONNECT_BY_PATH (n, ','), ',') permutations
FROM (SELECT LEVEL l, SUBSTR ('&col', LEVEL, 1) n
FROM DUAL
CONNECT BY LEVEL <= LENGTH ('&col')) yourtable
CONNECT BY NOCYCLE l != PRIOR l)
WHERE LENGTH (permutations) = LENGTH ('&col')
In the example col is defined in SQL*Plus, but if you make this a function you can pass it in, or could rework it to take a table column directly I suppose.
I'd take that as a start point rather than a solution; the original question was about anagrams so it's designed to find all permutations, so something similar but simplified might be possible. I suspect this doesn't scale very well for large values.
So eventually I went PL/SQL route, because after searching for some time I realized that there is no build-in function that I can use.
Here is what I came up with. Its based on the future of associative array which is that Oracle keeps the keys in sorted order.
create or replace function sort_chars(p_string in varchar2) return varchar deterministic
as
rv varchar2(4000);
ch varchar2(1);
type vcArray is table of varchar(4000) index by varchar2(1);
sorted vcArray;
key varchar2(1);
begin
for i in 1 .. length(p_string)
loop
ch := substr(p_string, i, 1);
if (sorted.exists(ch))
then
sorted(ch) := sorted(ch) || ch;
else
sorted(ch) := ch;
end if;
end loop;
rv := '';
key := sorted.FIRST;
WHILE key IS NOT NULL LOOP
rv := rv || sorted(key);
key := sorted.NEXT(key);
END LOOP;
return rv;
end;
Simple performance test:
set timing on;
create table test_sort_fn as
select t1.object_name || rownum as test from user_objects t1, user_objects t2;
select count(distinct test) from test_sort_fn;
select count (*) from (select sort_chars(test) from test_sort_fn);
Table created.
Elapsed: 00:00:01.32
COUNT(DISTINCTTEST)
-------------------
384400
1 row selected.
Elapsed: 00:00:00.57
COUNT(*)
----------
384400
1 row selected.
Elapsed: 00:00:00.06
You could use the following query:
select listagg(letter)
within group (order by UPPER(letter), ASCII(letter) DESC)
from
(
select regexp_substr('gfedcbaGFEDCBA', '.', level) as letter from dual
connect by regexp_substr('gfedcbaGFEDCBA', '.', level) is not null
);
The subquery splits the string into records (single character each) using regexp_substr, and the outer query merges the records into one string using listagg, after sorting them.
Here you should be careful, because alphabetical sorting depends on your database configuration, as Cine pointed.
In the above example the letters are sorted ascending "alphabetically" and descending by ascii code, which - in my case - results in "aAbBcCdDeEfFgG".
The result in your case may be different.
You may also sort the letters using nlssort - it would give you better control of the sorting order, as you would get independent of your database configuration.
select listagg(letter)
within group (order by nlssort(letter, 'nls_sort=german')
from
(
select regexp_substr('gfedcbaGFEDCBA', '.', level) as letter from dual
connect by regexp_substr('gfedcbaGFEDCBA', '.', level) is not null
);
The query above would give you also "aAbBcCdDeEfFgG", but if you changed "german" to "spanish", you would get "AaBbCcDdEeFfGg" instead.
You should remember that there is no common agreement what "alphabetically" means. It all depends on which country it is, and who is looking at your data and what context it is in.
For instance in DK, there are a large number of different sortings of a,aa,b,c,æ,ø,å
per the alphabet: a,aa,b,c,æ,ø,å
for some dictionary: a,aa,å,b,c,æ,ø
for other dictionaries: a,b,c,æ,ø,aa,å
per Microsoft standard: a,b,c,æ,ø,aa,å
check out http://www.siao2.com/2006/04/27/584439.aspx for more info. Which also happens to be a great blog for issues as these.
Assuming you don't mind having the characters returned 1 per row:
select substr(str, r, 1) X from (
select 'CAB' str,
rownum r
from dual connect by level <= 4000
) where r <= length(str) order by X;
X
=
A
B
C
For people using Oracle 10g, select listagg within group won't work. The accepted answer does work, but it generates every possible permutation of the input string, which results in terrible performance - my Oracle database struggles with an input string only 10 characters long.
Here is another alternative working for Oracle 10g. It's similar to Jeffrey Kemp's answer, only the result isn't splitted into rows:
select replace(wm_concat(ch), ',', '') from (
select substr('CAB', level, 1) ch from dual
connect by level <= length('CAB')
order by ch
);
-- output: 'ABC'
wm_concat simply concatenates records from different rows into a single string, using commas as separator (that's why we are also doing a replace later).
Please note that, if your input string had commas, they will be lost. Also, wm_concat is an undocumented feature, and according to this answer it has been removed in Oracle 12c. Use it only if you're stuck with 10g and don't have a better option (such as listagg, if you can use 11g instead).
From Oracle 12, you can use:
SELECT *
FROM table_name t
CROSS JOIN LATERAL (
SELECT LISTAGG(SUBSTR(t.value, LEVEL, 1), NULL) WITHIN GROUP (
ORDER BY SUBSTR(t.value, LEVEL, 1)
) AS ordered_value
FROM DUAL
CONNECT BY LEVEL <= LENGTH(t.value)
)
Which, for the sample data:
CREATE TABLE table_name (value) AS
SELECT 'ZYX' FROM DUAL UNION ALL
SELECT 'HELLO world' FROM DUAL UNION ALL
SELECT 'aæøåbcæøåaæøå' FROM DUAL;
Outputs:
VALUE
ORDERED_VALUE
ZYX
XYZ
HELLO world
EHLLOdlorw
aæøåbcæøåaæøå
aabcåååæææøøø
If you want to change how the letters are sorted then use NLSSORT:
SELECT *
FROM table_name t
CROSS JOIN LATERAL (
SELECT LISTAGG(SUBSTR(t.value, LEVEL, 1), NULL) WITHIN GROUP (
ORDER BY NLSSORT(SUBSTR(t.value, LEVEL, 1), 'NLS_SORT = BINARY_AI')
) AS ordered_value
FROM DUAL
CONNECT BY LEVEL <= LENGTH(t.value)
)
Outputs:
VALUE
ORDERED_VALUE
ZYX
XYZ
HELLO world
dEHLLlOorw
aæøåbcæøåaæøå
aaåååbcøøøæææ
db<>fiddle here

How to generate a binary combinations of a character array

I have to generate a binary character string in Oracle-SQL.
It has some rules. it can contain letter (A-Z) + numbers (0-9)
like A1, A2, A3
(However, it can not start with the number. 1A, 2A is not wanted.)
You can think of all the letters of the alphabet like A, B, C.
it can contain letter (A-Z) + letter (A-Z)
like AA, AB, AC, AD
There is such a data set but it doesn't provide a smooth increase. Some intermediate values have been deleted
In that case, firstly, the deleted one will be generated
secondly, if all intermediate values are filled, the largest value can be generated.
I think it need a hexadecimal solution, but it can not increase in the order that it is desired to progress.
What kind of approach should I take?
Does anyone have an idea?
Thanks in advance
I developed almost same request, I modify my cases to your rules.
It starts with smallest letter is not assigned via for loop. It control that if letter-letter and letter-number combinations are full, then it pass next letter.
as
temp varchar2(2);
pn_num NUMBER;
cn_string constant varchar2(25) := 'abcdefghijklmnoprstuvwxyz';
cn_string2 constant varchar2(35) := 'abcdefghijklmnoprstuvwxyz0123456789';
begin
pn_num := -1;
ps_seri_num := '-1';
for i in 1..25
loop
for j in 1..35
loop
select
upper( substr(cn_string,i,1) ||
substr(cn_string2,j,1))
into temp from dual;
select count(seri)
into pn_num
from bankdb.vrgdaire
where seri = temp;
IF pn_num = 0 THEN
--aa yok
ps_seri_num := temp;
exit;
END IF;
end loop;
IF ps_seri_num <> '-1' THEN
exit;
END IF;
end loop;
Let's define 2 views first:
LETTERS_VIEW from
select 'A' as sign from dual
union
select 'B' as sign from dual
...
union
select 'Z' as sign from dual
And LETTERS_DIGITS_VIEW from
select sign from LETTERS_VIEW
union
select '0' as sign from dual
...
union
select '9' as sign from dual
Then you can use
select concat(l1.sign, l2.sign ... lN.sign) as hex
from LETTERS_VIEW l1, LETTERS_DIGITS_VIEW l2, ... LETTERS_DIGITS_VIEW lN
There N is max length of the string
If you need some number you can order by the hex and choose N-th row(s)

Printing the position of the bigger letter in a list of characters

I am having some logic thinking trouble with this task.
So the task asks to return the position of the first bigger letter in a list of letters.
For example:
ABVD -> 3
BCDG -> 4
CFDE -> 2
This tasks suggests to use lenght, ascii, and named block, function
So this is what I could do so far:
declare
x varchar2(10) :='ABFD';
BEGIN
FOR i in 1..length(x) LOOP
dbms_output.put_line(ASCII(SUBSTR(x, i, 1)));
END LOOP;
END;
My thought was to turn the letters to numbers : 65, 66, 70, 68. The pattern is x + 1 and since the number 70 is not equal 66 + 1, so the program will return the position of that number, which is 3.
Unfortunately I don't know how turn this idea into code. Can you give me some hints/suggestions? Thanks!
In the problem statement you said "... use named block, function."
Your solution is an anonymous procedure. It is not named anywhere (which is why it is called "anonymous"). And it is not a function - it doesn't return anything.
I will let you study the documentation to understand the difference between function and procedure, and how to name a function or procedure. Below I will follow your lead and show how you can modify your code to make it into a workable anonymous procedure. (In the procedure I "print" the final value of ind; when you change this to a function, you should return that value, instead of printing it.)
In the code you posted, you are printing the letters in the input string, one by one. You are not even attempting to define or assign to an integer (the index of the first occurrence of the "highest" letter in the string). That should be done in the DECLARE block. Then we also need to store the highest letter found "so far" (for future comparisons).
The code might look like this:
declare
x varchar2(10) :='ABFD';
ind number := 1;
max_letter char(1) := substr(x, 1, 1);
BEGIN
FOR i in 2..length(x) LOOP
if substr(x, i, 1) > max_letter
then max_letter := substr(x, i, 1);
ind := i;
end if;
END LOOP;
dbms_output.put_line(ind);
END;
/
Note that letters can be compared to each other directly, there is no reason to convert them to numbers.
Pure SQL using model clause
with t(str) as
(select 'ABVD' from dual
union all select 'BCDG' from dual
union all select 'CFDE' from dual)
select str, instr(str, max_chr) ind
from t
model
partition by (rownum rn)
dimension by (1 dummy)
measures (str, chr(1) max_chr)
rules
iterate (4e3) until (substr(str[1], iteration_number + 2, 1) is null)
(max_chr[1] = greatest(max_chr[1], substr(str[1], iteration_number + 1, 1)));
STR IND
---- ----------
ABVD 3
BCDG 4
CFDE 2

PL SQL replace conditionally suggestion

I need to replace the entire word with 0 if the word has any non-digit character. For example, if digital_word='22B4' then replace with 0, else if digital_word='224' then do not replace.
SELECT replace_funtion(digital_word,'has non numeric character pattern',0,digital_word)
FROM dual;
I tried decode, regexp_instr, regexp_replace but could not come up with the right solution.
Please advise.
Thank you.
the idea is simple - you need check if the value is numeric or not
script:
with nums as
(
select '123' as num from dual union all
select '456' as num from dual union all
select '7A9' as num from dual union all
select '098' as num from dual
)
select n.*
,nvl2(LENGTH(TRIM(TRANSLATE(num, ' +-.0123456789', ' '))),'0',num)
from nums n
result
1 123 123
2 456 456
3 7A9 0
4 098 098
see more articles below to see which way is better to you
How can I determine if a string is numeric in SQL?
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:15321803936685
How to tell if a value is not numeric in Oracle?
You might try the following:
SELECT CASE WHEN REGEXP_LIKE(digital_word, '\D') THEN '0' ELSE digital_word END
FROM dual;
The regular expression class \D matches any non-digit character. You could also use [^0-9] to the same effect:
SELECT CASE WHEN REGEXP_LIKE(digital_word, '\D') THEN '0' ELSE digital_word END
FROM dual;
Alternately you could see if the value of digital_word is made up of nothing but digits:
SELECT CASE WHEN REGEXP_LIKE(digital_word, '^\d+$') THEN digital_word ELSE '0' END
FROM dual;
Hope this helps.
The fastest way is to replace all digits with null (to simply delete them) and see if anything is left. You don't need regular expressions (slow!) for this, you just need the standard string function TRANSLATE().
Unfortunately, Oracle has to work around their own inconsistent treatment of NULL - sometimes as empty string, sometimes not. In the case of the TRANSLATE() function, you can't simply translate every digit to nothing; you must also translate a non-digit character to itself, so that the third argument is not an empty string (which is treated as a real NULL, as in relational theory). See the Oracle documentation for the TRANSLATE() function. https://docs.oracle.com/cd/E11882_01/server.112/e41084/functions216.htm#SQLRF06145
Then, the result can be obtained with a CASE expression (or various forms of NULL handling functions; I prefer CASE, which is SQL Standard):
with
nums ( num ) as (
select '123' from dual union all
select '-56' from dual union all
select '7A9' from dual union all
select '0.9' from dual
)
-- End of simulated inputs (for testing only, not part of the solution).
-- SQL query begins BELOW THIS LINE. Use your own table and column names.
select num,
case when translate(num, 'z0123456789', 'z') is null
then num
else '0'
end as result
from nums
;
NUM RESULT
--- ------
123 123
-56 0
7A9 0
0.9 0
Note: everything here is in varchar2 data type (or some other kind of string data type). If the results should be converted to number, wrap the entire case expression within TO_NUMBER(). Note also that the strings '-56' and '0.9' are not all-digits (they contain non-digits), so the result is '0' for both. If this is not what you needed, you must correct the problem statement in the original post.
Something like the following update query will help you:
update [table] set [col] = '0'
where REGEXP_LIKE([col], '.*\D.*', 'i')

Display count of characters repeated in a string using SQL only

I want to achieve below code snippet o/p using select query alone. Is it possible without using regexp?
Character Count when string input is dynamic.
DECLARE
str VARCHAR2(255);
lv_val NUMBER;
lv_char CHAR(1);
lv_unq VARCHAR2(255);
BEGIN
str:= :p_string;
FOR i IN 1..length(str)
LOOP
lv_val := 0;
lv_char := SUBSTR(str,i,1);
IF instr(lv_unq,lv_char)>0 THEN
NULL;
ELSE
lv_unq := lv_unq||lv_char;
lv_val := ((LENGTH(str) - LENGTH(REPLACE(replace(str,' ',''), lv_char, ''))) / LENGTH(lv_char));
--select ((length(str) - LENgth(REPLACE(str, lv_char, ''))) / LENgth(lv_char)) into lv_val FROM dual;
DBMS_OUTPUT.PUT_LINE('Character '||lv_char || ' is repeated :'||lv_val||' times in the string '||str);
END IF;
END LOOP;
END;
Answering to the question's title:
Display count of characters repeated in a string using SQL only
with v as (select substr('hello world', level, 1) c from dual connect by level < 12),
d as (select chr(ascii('a')+level-1) c from dual connect by level <= 26)
select d.c, count(v.c) from d left join v on d.c = v.c
group by d.c
order by d.c;
See http://sqlfiddle.com/#!4/d41d8/38321/0 for the result
The first view split your string into characters. The second view is just the alphabet. Once you have both views, you only need a simple left join with a group by clause to count the number of matching occurrences.
Please note:
in the first view, the string and its length are hard-coded in this example
I assume all your characters are lower-case
I only take into account the 26 (lower case) letters of the ASCII encoding.