Oracle SQL REGEXP for finding a value between columns, irrespective of their position - sql

I have following set of data in two different columns in a table. I need to check if the BB% value is matching between both the columns or not.
Column A
Column B
BB12,AA13,CC24
AA13,BB12,CC24
BB99,AA34,CC78
AA34,CC78,BB77
AA22,BB33,CC77
AA22,BB33,BB44,CC77
I initially tried using below REGEXP. But this will only work if the BB value in column A is always in position 1 and in Column B with position 2. But I need a REGEXP to check something for row 2 and row 3 scenarios.
REGEXP_SUBSTR(ColumnA, '\w+', 1, 1) <> REGEXP_SUBSTR(ColumnB, '\w+', 1, 2)

You can try this. But, it does not take account the case there are more than one occurrence of BBXXX in the columnA. In such a case, the comparison will only be based on the first occurence of BBXXX in the columnA.
with your_table (ColumnA, ColumnB) as (
select 'BB12,AA13,CC24', 'AA13,BB12,CC24' from dual union all
select 'BB99,AA34,CC78', 'AA34,CC78,BB77' from dual union all
select 'AA22,BB33,CC77', 'AA22,BB33,BB44,CC77' from dual union all
select 'AA22,BB33,CC77', 'AA22,BB44,BB33,CC77' from dual union all
select 'AA22,BB33,CC77', 'BB33,AA22,BB44,CC77' from dual
)
select COLUMNA, COLUMNB
from your_table t
where ','||COLUMNB||',' NOT like '%,'||regexp_substr(COLUMNA, 'BB[^,]*', 1, 1)||',%'
;

I believe this will do the trick and allow for the value in COLB to be in any position. Return the rows where the first "BB" element in COLA is not in COLB. Note row 3 has the "BB" values swapped from the original example to show order does not matter.
WITH tbl(ID, cola, colb) AS (
SELECT 1, 'BB12,AA13,CC24','AA13,BB12,CC24' FROM dual UNION ALL
SELECT 2, 'BB99,AA34,CC78','AA34,CC78,BB77' FROM dual UNION ALL
SELECT 3, 'AA22,BB33,CC77','AA22,BB44,BB33,CC77' FROM dual
)
SELECT ID, COLA, COLB
FROM tbl T
WHERE NOT REGEXP_LIKE(COLB, REGEXP_SUBSTR(COLA, 'BB[^,]*'));
ID COLA COLB
---------- -------------- -------------------
2 BB99,AA34,CC78 AA34,CC78,BB77

I suggest extracting the BBxx substring from each string and then compare the two for equality or inequality:
WITH cteData(ColumnA,ColumnB) AS
(SELECT 'BB12,AA13,CC24', 'AA13,BB12,CC24' FROM DUAL UNION ALL
SELECT 'BB99,AA34,CC78', 'AA34,CC78,BB77' FROM DUAL UNION ALL
SELECT 'AA22,BB33,CC77', 'AA22,BB33,BB44,CC77' FROM DUAL)
SELECT COLUMNA,
REGEXP_SUBSTR(COLUMNA, 'BB[^,]+') AS COLA_BB,
COLUMNB,
REGEXP_SUBSTR(COLUMNB, 'BB[^,]+') AS COLB_BB
FROM cteData
WHERE REGEXP_SUBSTR(COLUMNA, 'BB[^,]+') = REGEXP_SUBSTR(COLUMNB, 'BB[^,]+')
dbfiddle here

Related

Find value that is not a number or a predefined string

I have to test a column of a sql table for invalid values and for NULL.
Valid values are: Any number and the string 'n.v.' (with and without the dots and in every possible combination as listed in my sql command)
So far, I've tried this:
select count(*)
from table1
where column1 is null
or not REGEXP_LIKE(column1, '^[0-9,nv,Nv,nV,NV,n.v,N.v,n.V,N.V]+$');
The regular expression also matches the single character values 'n','N','v','V' (with and without a following dot). This shouldn't be the case, because I only want the exact character combinations as written in the sql command to be matched. I guess the problem has to do with using REGEXP_LIKE. Any ideas?
I guess this regexp will work:
NOT REGEXP_LIKE(column1, '^([0-9]+|n\.?v\.?)$', 'i')
Note that , is not a separator, . means any character, \. means the dot character itself and 'i' flag could be used to ignore case instead of hard coding all combinations of upper and lower case characters.
No need to use regexp (performance will increase by large data) - plain old TRANSLATE is good enough for your validation.
Note that the first translate(column1,'x0123456789','x') remove all numeric charcters from the string, so if you end with nullthe string is OK.
The second translate(lower(column1),'x.','x') removes all dots from the lowered string so you expect the result nv.
To avoid cases as n.....v.... you also limit the string length.
select
column1,
case when
translate(column1,'x0123456789','x') is null or /* numeric string */
translate(lower(column1),'x.','x') = 'nv' and length(column1) <= 4 then 'OK'
end as status
from table1
COLUMN1 STATUS
--------- ------
1010101 OK
1012828n
1012828nv
n.....v....
n.V OK
Test data
create table table1 as
select '1010101' column1 from dual union all -- OK numbers
select '1012828n' from dual union all -- invalid
select '1012828nv' from dual union all -- invalid
select 'n.....v....' from dual union all -- invalid
select 'n.V' from dual; -- OK nv
You can use:
select count(*)
from table1
WHERE TRANSLATE(column1, ' 0123456789', ' ') IS NULL
OR LOWER(column1) IN ('nv', 'n.v', 'nv.', 'n.v.');
Which, for the sample data:
CREATE TABLE table1 (column1) AS
SELECT '12345' FROM DUAL UNION ALL
SELECT 'nv' FROM DUAL UNION ALL
SELECT 'NV' FROM DUAL UNION ALL
SELECT 'nV' FROM DUAL UNION ALL
SELECT 'n.V.' FROM DUAL UNION ALL
SELECT '...................n.V.....................' FROM DUAL UNION ALL
SELECT '..nV' FROM DUAL UNION ALL
SELECT 'n..V' FROM DUAL UNION ALL
SELECT 'nV..' FROM DUAL UNION ALL
SELECT 'xyz' FROM DUAL UNION ALL
SELECT '123nv' FROM DUAL;
Outputs:
COUNT(*)
5
or, if you want any quantity of . then:
select count(*)
from table1
WHERE TRANSLATE(column1, ' 0123456789', ' ') IS NULL
OR REPLACE(LOWER(column1), '.') = 'nv';
Which outputs:
COUNT(*)
9
db<>fiddle here

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>

In SQL sort by Alphabets first then by Numbers

In H2 Database when i have applied order by on varchar column Numbers are coming first then Alphabets. But need to come Alphabets first then Numbers.
I have tried with
ORDER BY IF(name RLIKE '^[a-z]', 1, 2), name
but getting error like If condition is not available in H2.
My Column Data is Like
A
1-A
3
M
2-B
5
B-2
it should come like
A
B-2
M
1-A
2-B
3
5
try this out
SELECT MYCOLUMN FROM MYTABLE ORDER BY REGEXP_REPLACE (MYCOLUMN,'(*)(\d)(*)','}\2') , MYCOLUMN
One thing can be done is by altering the ASCII in order by clause.
WITH tab
AS (SELECT 'A' col FROM DUAL
UNION ALL
SELECT '1-A' FROM DUAL
UNION ALL
SELECT '3' FROM DUAL
UNION ALL
SELECT 'M' FROM DUAL
UNION ALL
SELECT '2-B' FROM DUAL
UNION ALL
SELECT '5' FROM DUAL
UNION ALL
SELECT 'B-2' FROM DUAL)
SELECT col
FROM tab
ORDER BY CASE WHEN SUBSTR (col, 1, 1) < CHR (58) THEN CHR (177) || col ELSE col END;
I have Used CHR(58) as ASCII value of numbers end at 57. and CHR(177) is used as this is the maximum in the ASCII table.
FYR : ASCII table
Given the example dataset, I'm not sure if you need further logic than this- so I'll refrain from making further assumptions:
DECLARE #temp TABLE (myval char(3))
INSERT INTO #temp VALUES
('A'), ('1-A'), ('3'), ('M'), ('2-B'), ('5'), ('B-2')
SELECT myval
FROM #temp
ORDER BY CASE WHEN LEFT(myval, 1) LIKE '[a-Z]'
THEN 1
ELSE 2
END
,LEFT(myval, 1)
Gives output:
myval
A
B-2
M
1-A
2-B
3
5

Retrieve certain number from data set in Oracle 10g

1. <0,0><120.96,2000><241.92,4000><362.88,INF>
2. <0,0><143.64,2000><241.92,4000><362.88,INF>
3. <0,0><125.5,2000><241.92,4000><362.88,INF>
4. <0,0><127.5,2000><241.92,4000><362.88,INF>
Above is the data set I have in Oracle 10g. I need output as below
1. 120.96
2. 143.64
3. 125.5
4. 125.5
the output I want is only before "comma" (120.96). I tried using REGEXP_SUBSTR but I could not get any output. It will be really helpful if someone could provide effective way to solve this
Here is one method that first parses out the second element and then gets the first number in it:
select regexp_substr(regexp_substr(x, '<[^>]*>', 1, 2), '[0-9.]+', 1, 1)
Another method just gets the third number in the string:
select regexp_substr(x, '[0-9.]+', 1, 3)
Here is an approach without using Regexp.
Find the index of second occurrence of '<'. Then find the second occurrence of ',' use those values in substring.
with
data as
(
select '<0,0><120.96,2000><241.92,4000><362.88,INF>' x from dual
UNION ALL
select '<0,0><143.64,2000><241.92,4000><362.88,INF>' x from dual
UNION ALL
select '<0,0><125.5,2000><241.92,4000><362.88,INF>' from dual
)
select substr(x, instr(x,'<',1,2)+1, instr(x,',',1,2)- instr(x,'<',1,2)-1)
from data
Approach Using Regexp:
Identify the 2nd occurence of numerical value followed by a comma
Then remove the trailing comma.
with
data as
(
select '<0,0><120.96,2000><241.92,4000><362.88,INF>' x from dual
UNION ALL
select '<0,0><143.64,2000><241.92,4000><362.88,INF>' x from dual
UNION ALL
select '<0,0><125.5,2000><241.92,4000><362.88,INF>' from dual
)
select
trim(TRAILING ',' FROM regexp_substr(x,'[0-9.]+,',1,2))
from data
This example uses regexp_substr to get the string contained within the 2nd occurance of a less than sign and a comma:
SQL> with tbl(id, str) as (
select 1, '<0,0><120.96,2000><241.92,4000><362.88,INF>' from dual union
select 2, '<0,0><143.64,2000><241.92,4000><362.88,INF>' from dual union
select 3, '<0,0><125.5,2000><241.92,4000><362.88,INF>' from dual union
select 4, '<0,0><127.5,2000><241.92,4000><362.88,INF>' from dual
)
select id,
regexp_substr(str, '<(.*?),', 1, 2, null, 1) value
from tbl;
ID VALUE
---------- -------------------------------------------
1 120.96
2 143.64
3 125.5
4 127.5
EDIT: I realized the OP specified 10g and the regexp_substr example I gave used the 6th argument (subgroup) which was added in 11g. Here is an example using regexp_replace instead which should work with 10g:
SQL> with tbl(id, str) as (
select 1, '<0,0><120.96,2000><241.92,4000><362.88,INF>' from dual union
select 2, '<0,0><143.64,2000><241.92,4000><362.88,INF>' from dual union
select 3, '<0,0><125.5,2000><241.92,4000><362.88,INF>' from dual union
select 4, '<0,0><127.5,2000><241.92,4000><362.88,INF>' from dual
)
select id,
regexp_replace(str, '^(.*?)><(.*?),.*$', '\2') value
from tbl;
ID VALUE
---------- ----------
1 120.96
2 143.64
3 125.5
4 127.5
SQL>

Oracle Order By Sorting: Column Values with character First Followed by number

I have column values as
AVG,ABC, AFG, 3/M, 150,RFG,567, 5HJ
Requirement is to sort as below:
ABC,AFG,AVG,RFG,3/M,5HJ,150,567
Any help?
If you want to sort letters before numbers, then you can test each character. Here is one method:
order by (case when substr(col, 1, 1) between 'A' and 'Z' then 1 else 2 end),
(case when substr(col, 2, 1) between 'A' and 'Z' then 1 else 2 end),
(case when substr(col, 3, 1) between 'A' and 'Z' then 1 else 2 end),
col
This doesn't produce the requested output, but for lexicographic with numbers second TRANSLATE is a simple solution:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions196.htm
select value
from (
select 'AVG' as value from dual
union all
select 'ABC' as value from dual
union all
select 'AFG' as value from dual
union all
select '3/M' as value from dual
union all
select '150' as value from dual
union all
select 'RFG' as value from dual
union all
select '567' as value from dual
union all
select '5HJ' as value from dual
)
order by translate(upper(value), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
;
This shifts all the letters down and numbers to the end.
Unfortunately within the sort order numbers are before chars.
I could suggest you the put an additional calculated column where you are adding 'ZZZ' in front of the values if they start with a number then you will sort by that virtual column
If there aren't a large number of unique values, build a table that has the value and it's artificial sort order, then order by the sort key. Something like:
create table sort_map
( value varchar2(35),
sort_order number(4)
);
insert into sort_map (value, sort_order) values ('ABC',10);
insert into sort_map (value, sort_order) values ('AFG', 20);
....
insert into sort_map (value, sort_order) values ('150', 70);
insert into sort_map (value, sort_order) values ('567', 80);
--example query
select t.my_col, s.sort_order
from my_table t
join sort_map s
on (t.my_col = s.value)
order by s.sort_order;
A) If you only want to change the order of full numberic secuences just create your isNumeric function:
SELECT * FROM table WHERE isNumeric(field) ORDER BY FIELD
UNION ALL
SELECT * FROM table WHERE NOT isNumeric(field) ORDER BY FIELD
B) If you want to change the order of characters.
Create a funtion that adds a number before every character with a modifier.
Number -> 0
Other -> 2
Letter -> 4
For example:
shortHelper("2FT/") => "024F4T2/"
shortHelper("AZ") => "4A4Z"
shortHelper("Z1") => "4Z01"
Then use "ORDER BY shortHelper(Field)