How to generate a binary combinations of a character array - sql

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)

Related

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

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

License check with REGEXP in PL-SQL Oracle Apex

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

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.

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