I'm trying to use the Oracle REGEXP_REPLACE function to replace a whitespace (which is in the middle of a string) with an empty string.
One of my columns contains strings like the following one.
[alphanumeric][space][digits][space][alpha] (eg. R4SX 315 GFX)
Now, I need to replace ONLY the second whitespace (the whitespace after the digits) with an empty string (i.e. R4SX 315 GFX --> R4SX 315GFX)
To achieve this, I tried the following code:
SELECT REGEXP_REPLACE(
'R4SX 315 GFX',
'([:alphanum:])\s(\d)\s([:alpha:])',
'\1 \2\3') "REPLACED"
FROM dual;
However, the result that I get is the same as my input (i.e. R4SX 315 GFX).
Can someone please tell me what I have done wrong and please point me in the right direction.
Thanks in advance.
[:alphanum:]
alphanum is incorrrect. The alphanumeric character class is [[:alnum:]].
You could use the following pattern in the REGEXP_REPLACE:
([[:alnum:]]{4})([[:space:]]{1})([[:digit:]]{3})([[:space:]]{1})([[:alpha:]]{3})
Using REGEXP
SQL> SELECT REGEXP_REPLACE('R4SX 315 GFX',
2 '([[:alnum:]]{4})([[:space:]]{1})([[:digit:]]{3})([[:space:]]{1})([[:alpha:]]{3})',
3 '\1\2\3\5')
4 FROM DUAL;
REGEXP_REPL
-----------
R4SX 315GFX
SQL>
If you are not sure about the number of characters in each expression of the pattern, then you could do:
SQL> SELECT REGEXP_REPLACE('R4SX 315 GFX',
2 '([[:alnum:]]+[[:blank:]]+[[:digit:]]+)[[:blank:]]+([[:alpha:]]+)',
3 '\1\2')
4 FROM dual;
REGEXP_REPL
-----------
R4SX 315GFX
SQL>
Using SUBSTR and INSTR
The same could be done with substr and instr which wouldbe less resource consuming than regexp.
SQL> WITH DATA AS
2 ( SELECT 'R4SX 315 GFX' str FROM DUAL
3 )
4 SELECT SUBSTR(str, 1, instr(str, ' ', 1, 2) -1)
5 ||SUBSTR(str, instr(str, ' ', 1, 2) +1, LENGTH(str)-instr(str, ' ', 1, 2)) new_str
6 FROM DATA;
NEW_STR
-----------
R4SX 315GFX
SQL>
Your regex contains an invalid class alphanum. Also, these classes must be used inside character classes [...]. Instead of \s, you need to use a supported [:blank:] class. More details on the regex syntax in MySQL can be found here.
I recommend using
SELECT REGEXP_REPLACE(
'R4SX 315 GFX',
'([[:alnum:]]+[[:blank:]]+[[:digit:]]+)[[:blank:]]+([[:alpha:]]+)'
, '\1\2') "REGEXP_REPLACE"
FROM dual;
This way you will use just 2 capturing groups. The less we have the better is for performance. Here you can see more details on REGEXP_REPLACE function.
Related
I have a string containing codes like 'code1 code2 code3'. It should return the string if all codes entered are contained in the string.
For example:
select * from (
select 'avs cde jkl' code from dual)
where REGEXP_LIKE(code, 'REGEX-MAGIC')
When the regex is now something like ^(?=.*\bjkl\b)(?=.*\bavs\b).*$ then it should return the code. But this syntax is not working for regex in oracle.
The logic is 'if all codes looked for are in the string (order does not matter), then return the code.'
I have researched and this would be achievable with a positive lookahead, but oracle does not support this as far as I know. I would search for one regex and not a construct like REGEXP_LIKE(...,..) and REGEXP_LIKE(...,..) and ....
The Oracle Version is 12c.
Any help would be appreciated!
Oracle does not support look-ahead, look-behind or word boundaries in regular expressions.
If you have the sample data:
CREATE TABLE table_name (code) AS
SELECT 'avs cde jkl' FROM DUAL UNION ALL
SELECT 'avs cde' FROM DUAL UNION ALL
SELECT 'jkl avs' FROM DUAL UNION ALL
SELECT 'cde jkl' FROM DUAL;
Option 1:
The simplest query is to not use regular expressions and to look for sub-string matches using multiple LIKE conditions:
SELECT code
FROM table_name
WHERE ' ' || code || ' ' LIKE '% avs %'
AND ' ' || code || ' ' LIKE '% jkl %'
Which outputs:
CODE
avs cde jkl
jkl avs
Option 2:
You could use (slower) regular expressions with multiple REGEXP_LIKE conditions:
SELECT code
FROM table_name
WHERE REGEXP_LIKE(code, '(^| )avs( |$)')
AND REGEXP_LIKE(code, '(^| )jkl( |$)')
Which outputs the same as above.
Option 3:
You could put the matches into a sub-query factoring clause and then use a LATERAL join:
WITH match_conditions (match) AS (
SELECT 'avs' FROM DUAL UNION ALL
SELECT 'jkl' FROM DUAL
)
SELECT code
FROM table_name t
CROSS JOIN LATERAL (
SELECT 1
FROM match_conditions
WHERE ' ' || code || ' ' LIKE '% ' || match || ' %'
HAVING COUNT(*) = (SELECT COUNT(*) FROM match_conditions)
)
Which outputs the same as above.
Option 4:
If you really want a single regular expression then you can generate each permutation of the codes to match and concatenate them into a single regular expression:
SELECT code
FROM table_name
WHERE REGEXP_LIKE(
code,
'(^| )avs( | .*? )jkl( |$)' -- Permutation 1
|| '|(^| )jkl( | .*? )avs( |$)' -- Permutation 2
)
Which outputs the same as above.
However, this is going to get problematic to maintain as the number of codes to match grows as, for 2 items there are 2 permutations but for 5 items there are 5! = 120 permutations.
Option 5:
You could declare a nested table collection:
CREATE TYPE string_list AS TABLE OF VARCHAR2(20);
Then split the string (again, you do not need slow regular expressions) and then compare it to a nested table:
WITH bounds (rid, code, spos, epos) AS (
SELECT ROWID, code, 1, INSTR(code, ' ', 1)
FROM table_name
UNION ALL
SELECT rid, code, epos + 1, INSTR(code, ' ', epos + 1)
FROM bounds
WHERE epos > 0
)
SEARCH DEPTH FIRST BY code SET order_rn
SELECT MAX(code) AS code
FROM bounds
GROUP BY rid
HAVING string_list('avs', 'jkl') SUBMULTISET OF CAST(
COLLECT(
CAST(
CASE epos
WHEN 0
THEN SUBSTR(code, spos)
ELSE SUBSTR(code, spos, epos - spos)
END
AS VARCHAR2(20)
)
)
AS string_list
);
Depending on the client application you are using, you can pass the entire string_list('avs', 'jkl') collection in as a single bind variable that you can populate from an array. Java (and some languages built on top of Java) using an ODBC driver can do this; C# cannot directly but you can pass an associative array and convert it to a nested table collection with a helper function.
Which outputs the same as above.
db<>fiddle here
I'm not good at regex-magix, but - see if something like this helps.
This is a table that contains those codes:
SQL> select * from codes;
ID CODE
---------- -----------
1 avs cde jkl
2 xyz avs
Query
splits every code into rows (t_split CTE)
does the same for the entered parameter (par_string) value (p_split CTE)
why? So that they could act as if they were rows in a table, and you can apply the MINUS set operator
if MINUS returns nothing, there's a match; otherwise it's a mismatch
SQL> with
2 -- split code to rows
3 t_split as
4 (select id,
5 code original_code,
6 regexp_substr(code, '[^ ]+', 1, column_value) code
7 from codes cross join
8 table(cast(multiset(select level from dual
9 connect by level <= regexp_count(code, ' ') + 1
10 ) as sys.odcinumberlist))
11 where id = &&par_id
12 ),
13 -- split parameter to rows
14 p_split as
15 (select regexp_substr('&&par_string', '[^ ]+', 1, level) code
16 from dual
17 connect by level <= regexp_count('&&par_string', ' ') + 1
18 )
19 --
20 -- if all parameter's "pieces" of code are contained in CODE value, MINUS returns nothing
21 -- so there's a match
22 select distinct t.original_code,
23 '&&par_string' par_string,
24 case when (select count(*)
25 from (select code from t_split
26 minus
27 select code from p_split
28 )
29 ) = 0 then 'Match'
30 else 'Mismatch'
31 end result
32 from t_split t
33 where t.id = &&par_id;
Enter value for par_id: 1
Enter value for par_string: jkl avs cde
ORIGINAL_CO PAR_STRING RESULT
----------- ----------- --------
avs cde jkl jkl avs cde Match
SQL> undefine par_string
SQL> /
Enter value for par_string: avs jkl www
ORIGINAL_CO PAR_STRING RESULT
----------- ----------- --------
avs cde jkl avs jkl www Mismatch
SQL>
Depending on tool you use (this is SQL*Plus), you might need to replace && with a colon :; or, convert such a piece of code to a function.
I need help on how I can fully randomize the characters and numbers in the address without affecting the spaces. I tried the query below(PICTURE1) but it replaces all characters into 1 random character only. Any alternatives or logic to achieve the desired output? thanks
RESULT NEEDED SAMPLE ONLY:(random numbers/letters and same length and spacing position)
Try something like this (depending on your version, this can be simplified to make detecting numeric strings easier/faster):
with strings as (
select 'abc def 123 ktr' colv, 1 id from dual
union all
select 'abcdef gh 1trzzz' , 2 id from dual
)
select id,
(select listagg(case when length(regexp_replace(strd,'\d+','0'))=1 then
rpad(to_char(mod(abs(dbms_random.random()),power(10,length(strd)))),length(strd),'0')
else dbms_random.string('U',length(trim(strd))) end,' ')
from (select
trim(regexp_substr(colv,'(\w*\Z)|(\w* )',1,level)) strd from dual connect by level<=regexp_count(colv,' ')+1)
) rand_col
from strings
/
ID RAND_COL
---------- ------------------------------------------------------------
1 IYV MFS 609 SRV
2 GBLPUY LY BHIYNS
The idea is to split the strings into words, replace these words with random strings of equal size, and then reconstruct the string.
I want to get the time pattern along with AM or PM from the given string Aaaaa_gggg_ne_A030_66788_Abcd_Oct_24_0329PM.csv
I tried the following:
Select regexp_substr(filename,'\d{4}',1,3)
From
(Select 'Aaaaa_gggg_ne_A030_66788_Abcd_Oct_24_0329PM.csv' filename from dual);
which only gives me the last number, e.g. 0329, but I need 0329PM.
Using this form of REGEXP_SUBSTR() will get what you need in one call. It returns the first group, which is the set of characters after the last underscore and before the literal period of 1 or more numbers followed by an A or P then an M.
with tbl(filename) as (
Select 'Aaaaa_gggg_ne_A030_66788_Abcd_Oct_24_0329PM.csv'
from dual
)
select regexp_substr(filename, '_(\d+[AP]M)\.', 1, 1, NULL, 1)
From tbl;
Actually, to tighten up the match you could make it case-insensitive and add the extension:
select regexp_substr(filename, '_(\d+[AP]M)\.csv', 1, 1, 'i', 1)
From tbl;
Note if a match is not found NULL will be returned.
Nested substr is one option (if data always looks like this; you didn't say it doesn't):
SQL> with test (col) as
2 (select 'Aaaaa_gggg_ne_A030_66788_Abcd_Oct_24_0329PM.csv' from dual)
3 select substr(substr(col, -10), 1, 6) result from test
4 /
RESULT
------
0329PM
SQL>
the inner substr returns the last 10 characters (0329PM.csv)
the outer substr returns the first 6 characters out of it (0329PM)
Or, using regular expressions:
SQL> with test (col) as
2 (select 'Aaaaa_gggg_ne_A030_66788_Abcd_Oct_24_0329PM.csv' from dual)
3 select regexp_substr(translate(col, '_.', ' '), '\S+',
4 1,
5 regexp_count(translate(col, '_.', ' '), '\S+') - 1
6 ) result
7 from test;
RESULT
------
0329PM
SQL>
line #3: translate replaces underlines and dots with a space
line #4: start from the beginning
line #5: return substring which is one before the last one
I have to Ignore the leading zeros, leading/trailing spaces, leading alpha characters from a string. Please help what regexp can be used.
For example:
the string is abc123abc , then it needs to return 123abc.
Presently i used
REGEXP_SUBSTR('abc123abc','([1-9]+[0-9]*)( *)$')
but it returns null for me.
Something like this?
SQL> with test (col) as
2 (select 'abc123abc' from dual union all
3 select ' 1234ddc' from dual union all
4 select '0abcd' from dual union all
5 select '18858 ' from dual union all
6 select 'ab123ab45' from dual
7 )
8 select col, trim(regexp_replace(col, '^[[:alpha:]]+| |0')) result
9 from test;
COL RESULT
--------- ---------
abc123abc 123abc
1234ddc 1234ddc
0abcd abcd
18858 18858
ab123ab45 123ab45
SQL>
Vaish,
This is how the Regex should be.
What this will do, it will remove any leading spaces, leading zeros.
Example of query:
SELECT REGEXP_SUBSTR('abc123abc','[1-9]+.*') from dual;
You can see some examples I have tried here, plus you can test some more here too.
https://regex101.com/r/zfohRB/1
Regex: '[1-9]+.*'
Explanation:
[1-9] - This will look for the number to start. Excluding 0.
+ - Quantifier + denotes 1 or more.
. - Means anything after that.
* - Means 0 or more. (You can replace this with + if you think that you need at least something after numbers.)
Good Luck
I am trying to use REGEXP_REPLACE to replace/remove the first hyphen and 3 subsequent characters. Input table is not of fixed length and varies. I am trying to come up with a working express that will
5F9B9C7F-ABC-40F4
CODE-AXF 2014 CODE
ADSHLHSALK
Expected results results should be
5F9B9C7F-ABC-40F4 ==> 5F9B9C7F-40F4
CODE-AXF-2014 CODE ==> CODE- 2014 CODE
ADSHLHSALK ==> ADSHLHSALK
Query:
SELECT text, column
REGEXP_REPLACE( text,'[-]',NULL )
FROM TABLE
where column= '5';
You may use
REGEXP_REPLACE('5F9B9C7F-ABC-40F4','^([^-]*)-.{3}','\1')
If you mean 3 letters, then replace . with [a-zA-Z].
Details
^ - start of string
([^-]*) - Group 1: any 0+ chars other than -
- - a hyphen
.{3} - any 3 chars (or [a-zA-Z]{3} will match 3 ASCII letters).
See an online demo printing
You do not need regular expressions:
SELECT text,
column,
SUBSTR( text, 1, pos - 1 ) || SUBSTR( text, pos + 4 )
FROM (
SELECT text,
column,
INSTR( text, '-' ) AS pos
FROM TABLE
WHERE column = '5'
);