Issue when getting max value of an alphanumeric sequence in oracle - sql

I have an alphanumerical sequence from A0000 to ZZZ99 created in the following manner.
A0000 until Z9999. Then
AA000 until AZ999. Then
BA000 until ZZ999. Then
AAA00 until ZZZ99.
I want to get the max() value of the sequence but it is not coming in the same manner as it is created.
SELECT MAX(VAL) MAX_VAL FROM (select 'A0001' as val from dual union all
select 'A0087' as val from dual union all
select 'ABA00' as val from dual union all
select 'AAZ00' as val from dual union all
select 'B0032' as val from dual );
This sample query returns B0032 whereas i need ABA00 to be returned.
Maybe converting all characters as ASCII and then taking the max will help me solve the issue but i don't know how to change each character to ASCII dynamically in a query.
Any help will be appreciated. Thanks

You have to order your data first based on the first accurance of a digit and then get the max. In ORACLEyou can do that by the following query:
WITH dat AS (select 'A0001' as val from dual union all
select 'A0087' as val from dual union all
select 'ABA00' as val from dual union all
select 'AAZ00' as val from dual union all
select 'B0032' as val from dual)
SELECT MAX(val) KEEP (dense_rank last ORDER by REGEXP_INSTR(val, '\d')) FROM dat
Reason is that in lexigraphical order every word starting with an A is lower then any word starting with a B but in your case the first criterium is on which position occurs the first digit:
B0032 -> Position 2 and therefore B0032 < ABA00 which has Position 4.

Related

Regexp pattern for special characters

I have the data in the format like
Input:
Code_1
FAB
?
USP BEN,
.
-
,
Output:
Code_1
FAB
IP BEN,
I need to exclude only the value which have length as 1 and and are special characters
I am using (regexp_like(code_1,'^[^<>{}"/|;:.,~!?##$%^=&*\]\\()\[¿§«»ω⊙¤°℃℉€¥£¢¡®©0-9_+]')) AND LENGTH(CODE_1)>=1
I have also tried REGEXP_LIKE(CODE_1,'[A-Za-z0-9]')
Based on your requirements which I understand are you want data that is not single character AND non-alpha numeric (at the same time), this should do it for you.
The 'WITH' clause just sets up test data in this case and can be thought of like a temp table here. It is a great way to help people help you by setting up test data. Always include data you don't expect!
The actual query starts below and selects data that uses grouping to get the data that is NOT a group of non-alpha numeric with a length of one. It uses a POSIX shortcut of [:alnum:] to indicate [A-Za-z0-9].
Note your requirements will allow multiple non-alnum characters to be selected as is indicated by the test data.
WITH tbl(DATA) AS (
SELECT 'FAB' FROM dual UNION ALL
SELECT '?' FROM dual UNION ALL
SELECT 'USP BEN,' FROM dual UNION ALL
SELECT '.' FROM dual UNION ALL
SELECT '-' FROM dual UNION ALL
SELECT '----' FROM dual UNION ALL
SELECT ',' FROM dual UNION ALL
SELECT 'A' FROM dual UNION ALL
SELECT 'b' FROM dual UNION ALL
SELECT '5' FROM dual
)
SELECT DATA
FROM tbl
WHERE NOT (REGEXP_LIKE(DATA, '[^[:alnum:]]')
AND LENGTH(DATA) = 1);
DATA
----------
FAB
USP BEN,
----
A
b
5
6 rows selected.

Generate random value from a list of values with even distribution in Oracle

I am trying to generate a random value from a list of valid values [1,2,3,4,5,9]. If I run my function 10 times, there are 2 values that never appear. I need every value in the list to be present in my sample. How can I ensure this ? An equal distribution would be good, but at least few rows of every value.
select random_code
from (
with temp_code_table as (
select '1' as random_code from dual union
select '2' as random_code from dual union
select '3' as random_code from dual union
select '4' as random_code from dual union
select '5' as random_code from dual union
select '9' as random_code from dual)
select random_code from temp_code_table order by dbms_random.value)
where rownum = 1;
I am running the above SQL inside a function, which checks that the random code generated is not the same as the original value.
Edit: Not sure the answer in the below post will help me achieve what I want.
Generating Random Value using CASE
Any advice on how to achieve this ?
"A random number" and "at least one of each" cannot be combined. The definition of random is that you down't know what you'll get... Roll a dice 10 times. Are you sure you'll have thrown all sides at least once ? No.
So to solve your problem, you can select one of each first - that way you have those already and then fill up the rest with randomly selected numbers from the set.
with temp_code_table (nr) as
(
SELECT '1' FROM dual UNION
SELECT '2' FROM dual UNION
SELECT '3' FROM dual UNION
SELECT '4' FROM dual UNION
SELECT '5' FROM dual UNION
SELECT '9' FROM dual
)
-- now select one of each. order doesn't matter, we're doing that at the end
, one_of_each AS
(
SELECT nr FROM temp_code_table
)
-- ok we got at least one of each. Now fill up the rest with randoms
-- generate a list of hundred random values 1,2,3,4,5,9 (100 is chosen randomly)
, hundred_random AS
(
SELECT CASE round(dbms_random.value(1,6))
WHEN 1 THEN '1'
WHEN 2 THEN '2'
WHEN 3 THEN '3'
WHEN 4 THEN '4'
WHEN 5 THEN '5'
WHEN 6 THEN '9'
END AS nr
FROM DUAL connect by LEVEL < 101
)
-- select 4 out of those.
, four_more (nr) AS
(
SELECT nr FROM hundred_random WHERE rownum < 5
)
-- put it all together
, ten_rows AS
(
SELECT nr FROM one_of_each
UNION ALL
SELECT nr FROM four_more
)
-- and shuffle...
SELECT nr FROM ten_rows order by dbms_random.value
;
The "temp_code_table" cte generates exactly 6 rows. That way you have at least one of each number in the set.
The "hundred_random" cte uses the case statement on the random number which will generate a single random value of the set. Then that is run 100 times using CONNECT BY LEVEL. Out of those 100, 4 are picked. Those 4 could all be 1 - this is random, there is no guarantee that you have distinct numbers.
At the end we union the 6 rows of the temp_code_table cte and the 4 rows of the four_more and order them randomly.

Sort a value list that contains letters and also numbers in a specific order

I have a problem in SQL Oracle, I'm trying to create a view that contains values with letters and numbers and I want to sort them in a specific order.
Here is my query:
create or replace view table1_val (val, msg_text) as
select
val, msg_text
from
table_val
where
val in ('L1','L2','L3','L4','L5','L6','L7','L8','L9','L10','L11','L12','L13','L14','G1','G2','G3','G4')
order by lpad(val, 3);
The values are displayed like this:
G1,G2,G3,G4,L1,L2,L3,L4,L5,L6,L7,L8,L9,L10,L11,L12,L13
The thing is that I want to display the L values first and then the G values like in the where condition. The 'val' column is VARCHAR2(3 CHAR). The msg_text column is irrelevant. Can someone help me with that? I use Oracle 12C.
You must interpret the second part of the val column as a number
order by
case when val like 'L%' then 0 else 1 end,
to_number(substr(val,2))
This work fine for your current data, but may fail in future if a new record is added with non-numeric structure.
More conservative (and more hard to write), but safe would be to used a decode for all the current keys, ordering unknown keys on the last position (id = 18 in the example):
order by
decode(
'L1',1,
'L2',2,
'L3',3,
'L4',4,
'L5',5,
'L6',6,
'L7',7,
'L8',8,
'L9',9,
'L10',10,
'L11',11,
'L12',12,
'L13',13,
'G1',14,
'G2',15,
'G3',16,
'G4',17,18)
You can't do anything based on the order of the WHERE condition
But you can use a CASE on the ORDER BY
ORDER BY CASE
WHEN SUBSTR(val, 1, 1) = 'L' THEN 1
WHEN SUBSTR(val, 1, 1) = 'G' THEN 2
ELSE 3
END,
TO_NUMBER (SUBSTR(val, 2, 10));
Another option to consider might be using regular expressions, such as
SQL> with table1_val (val) as
2 (select 'L1' from dual union all
3 select 'L26' from dual union all
4 select 'L3' from dual union all
5 select 'L21' from dual union all
6 select 'L11' from dual union all
7 select 'L4' from dual union all
8 select 'G88' from dual union all
9 select 'G10' from dual union all
10 select 'G2' from dual
11 )
12 select val
13 from table1_val
14 order by regexp_substr(val, '^[[:alpha:]]+') desc,
15 to_number(regexp_substr(val, '\d+$'));
VAL
---
L1
L3
L4
L11
L21
L26
G2
G10
G88
9 rows selected.
SQL>

how to select exact 7 or 10 world in oracle using regular expression

I am working on below query, I am expected to select exact 7 or 10 digit values columns using regular expression, I have used express in regexp_like() function of oracle, but its not working, please help
Query :
select * from
(select '1234567CELL' "a" from dual
union
select '123CaLLAsasd12' "a" from dual
union
select 'as9960488188CELLas12' "a" from dual
union
select '1234567' "a" from dual
union
select '9960488188' "a" from dual
union
select 'asdCELLqw' "a" from dual) b
where b."a" like '%CELL%' and regexp_like(b."a",'^(\d{7}|\d{10})$');
Expected output
"1234567"
"9960488188"
as above two rows, please check
^ and $ match the start and end of a string and the value cannot contain the string CELL and be solely a 7- or 10-digit number. Instead you could use the regular expression (^|\D)(\d{7}|\d{10})($|\D) which will match either the start of the string or a not digit character (^|\D) then either 7- or 10- digits and then either the end of the string or a non digit character ($|\D).
Like this:
WITH data ( a ) AS (
select '1234567CELL' from dual union
select '123CaLLAsasd12' from dual union
select 'as9960488188CELLas12' from dual union
select '1234567' from dual union
select '9960488188' from dual union
select 'asdCELLqw' from dual
)
SELECT a,
REGEXP_SUBSTR( a, '(^|\D)(\d{7}|\d{10})($|\D)', 1, 1, NULL, 2 ) AS val
FROM data
WHERE a LIKE '%CELL%'
AND REGEXP_LIKE( a, '(^|\D)(\d{7}|\d{10})($|\D)');
Output:
A VAL
-------------------- ----------
1234567CELL 1234567
as9960488188CELLas12 9960488188
You may just use
where regexp_like(b."a",'^([[:digit:]]{7}|[[:digit:]]{10})$')
Since the pattern is anchored (^ matches the start of the string and $ matches the end of the string) there can't be CELL inside the entries you fetch, and you can remove where b."a" like '%CELL%' from the query.

Filter the rows with number only data in a column SQL

I am trying to SELECT rows in a table, by applying a filter condition of identifying number only columns. It is a report only query, so we least bother the performance, as we dont have the privilege to compile a PL/SQL am unable to check by TO_NUMBER() and return if it is numeric or not.
I have to achieve it in SQL. Also the column is having the values like this, which have to be treated as Numbers.
-1.0
-0.1
-.1
+1,2034.89
+00000
1023
After ground breaking research, I wrote this.(Hard time)
WITH dummy_data AS
( SELECT '-1.0' AS txt FROM dual
UNION ALL
SELECT '+0.1' FROM dual
UNION ALL
SELECT '-.1' FROM dual
UNION ALL
SELECT '+1,2034.89.00' FROM dual
UNION ALL
SELECT '+1,2034.89' FROM dual
UNION ALL
SELECT 'Deva +21' FROM dual
UNION ALL
SELECT '1+1' FROM dual
UNION ALL
SELECT '1023' FROM dual
)
SELECT dummy_data.*,
REGEXP_COUNT(txt,'.')
FROM dummy_data
WHERE REGEXP_LIKE (TRANSLATE(TRIM(txt),'+,-.','0000'),'^[-+]*[[:digit:]]');
I got this.
TXT REGEXP_COUNT(TXT,'.')
------------- ---------------------
-1.0 4
+0.1 4
-.1 3
+1,2034.89.00 13 /* Should not be returned */
+1,2034.89 10
1+1 3 /* Should not be returned */
1023 4
7 rows selected.
Now terribly confused with 2 Questions.
1) I get +1,2034.89.00 too in result, I should eliminate it. (means, two decimal points) Not just decimal point, double in every other special character (-+,) should be eliminated)
2) To make it uglier, planned to do a REGEXP_COUNT('.') <= 1. But it is not returning my expectation, while selecting it, I see strange values returned.
Can someone help me to frame the REGEXP for the avoiding the double occurences of ('.','+','-')
The following expression works for everything, except the commas:
'^[-+]*[0-9,]*[.]*[0-9]+$'
You can check for bad comma placement with additional checks like:
not regexp_like(txt, '[-+]*,$') and not regexp_like(txt, [',,'])
First you remove plus and minus with translate and then you wonder why their position is not considered? :-)
This should work:
WITH dummy_data AS
( SELECT '-1.0' AS txt FROM dual
UNION ALL
SELECT '+0.1' FROM dual
UNION ALL
SELECT '-.1' FROM dual
UNION ALL
SELECT '+12034.89.00' FROM dual -- invalid: duplicate decimal separator
UNION ALL
SELECT '+1,2034.89' FROM dual -- invalid: thousand separator placement
UNION ALL
SELECT 'Deva +21' FROM dual -- invalid: letters
UNION ALL
SELECT '1+1' FROM dual -- invalid: plus sign placement
UNION ALL
SELECT '1023' FROM dual
UNION ALL
SELECT '1.023,88' FROM dual -- invalid: decimal/thousand separators mixed up
UNION ALL
SELECT '1,234' FROM dual
UNION ALL
SELECT '+1,234.56' FROM dual
UNION ALL
SELECT '-123' FROM dual
UNION ALL
SELECT '+123,0000' FROM dual -- invalid: thousand separator placement
UNION ALL
SELECT '+234.' FROM dual -- invalid: decimal separator not followed by digits
UNION ALL
SELECT '12345,678' FROM dual -- invalid: missing thousand separator
UNION ALL
SELECT '+' FROM dual -- invalid: digits missing
UNION ALL
SELECT '.' FROM dual -- invalid: digits missing
)
select * from dummy_data
where regexp_like(txt, '[[:digit:]]') and
(
regexp_like(txt, '^[-+]{0,1}([[:digit:]]){0,3}(\,([[:digit:]]){0,3})*(\.[[:digit:]]+){0,1}$')
or
regexp_like(txt, '^[-+]{0,1}[[:digit:]]*(\.[[:digit:]]+){0,1}$')
);
You see, you need three regular expressions; one to guarantee that there is at least one digit in the string, one for numbers with thousand separators, and one for numbers without.
With thousand separators: txt may start with one plus or minus sign, then there may be up to three digits. These may be followed by a thousand separator plus three digits several times. Then there may be a decimal separator with at least one following number.
Without thousand separators: txt may start with one plus or minus sign, then there may be digits. Then there may be a decimal separator with at least one following number.
I hope I haven't overlooked anything.
I just tried to correct the mistakes of you and made the SQL simple as possible. But not neat!
WITH dummy_data AS
( SELECT '-1.0' AS txt FROM dual
UNION ALL
SELECT '+.0' FROM dual
UNION ALL
SELECT '-.1' FROM dual
UNION ALL
SELECT '+1,2034.89.0' FROM dual
UNION ALL
SELECT '+1,2034.89' FROM dual
UNION ALL
SELECT 'Deva +21' FROM dual
UNION ALL
SELECT 'DeVA 234 Deva' FROM dual
UNION ALL
SELECT '1023' FROM dual
)
SELECT to_number(REPLACE(txt,',')),
REGEXP_COUNT(txt,'.')
FROM dummy_data
WHERE REGEXP_LIKE (txt,'^[-+]*')
AND NOT REGEXP_LIKE (TRANSLATE(txt,'+,-.','0000'),'[^[:digit:]]')
AND REGEXP_COUNT(txt,',') <= 1
AND REGEXP_COUNT(txt,'\+') <= 1
AND REGEXP_COUNT(txt,'\-') <= 1
AND REGEXP_COUNT(txt,'\.') <= 1;