Oracle regexp_like word boundaries multiple words workaround - oracle

As you know Oracle POSIX implementation of regexes does not support word boundaries. One workaround is suggested here:
Oracle REGEXP_LIKE and word boundaries
However it does not work if I want to, for instance select all 4 character strings. Consider this, for example:
myvar:=regexp_substr('test test','(^|\s|\W)[\S]{4}($|\s|\W)')
This obviously selects only the first occurrence. I do not know how to do this in the Oracle world, although normally it is simply (\b)[\S]{4}(\b). The problem is that most woraround rely on some nonexistent feature, like lookaround etc.

select xmlcast(xmlquery('for $token in ora:tokenize(concat(" ",$in)," ")
where string-length($token) = $size
return $token' passing 'test test' as "in", 4 as "size" returning content) as varchar2(2000)) word from dual;
Xquery and FLWOR expresion.
concat(" ",$in) - workaround if input string is null or it has only 1 matchting word.
ora:tokenize - tokenize string by "space"
string-length($token) = $size check if token has appropriate length.
xmlcast - convert xmltype to varchar2
Easy ? Any question:)

DECLARE
str VARCHAR2(200) := 'test test';
pattern VARCHAR2(200) := '(\w+)($|\s+|\W+)';
match VARCHAR2(200);
BEGIN
FOR i IN 1 .. REGEXP_COUNT( str, pattern ) LOOP
match := REGEXP_SUBSTR( str, pattern, 1, i, NULL, 1 );
IF LENGTH( match ) = 4 THEN
DBMS_OUTPUT.PUT_LINE( match );
END IF;
END LOOP;
END;
/
or (without using REGEXP_COUNT or the 6th parameter of REGEXP_SUBSTR that was introduced in 11G):
DECLARE
str VARCHAR2(200) := 'test test';
pattern CONSTANT VARCHAR2(3) := '\w+';
match VARCHAR2(200);
i NUMBER(4,0) := 1;
BEGIN
match := REGEXP_SUBSTR( str, pattern, 1, i );
WHILE match IS NOT NULL LOOP
IF LENGTH( match ) = 4 THEN
DBMS_OUTPUT.PUT_LINE( match );
END IF;
i := i + 1;
match := REGEXP_SUBSTR( str, pattern, 1, i );
END LOOP;
END;
/
Output:
test
test
If you want to use this in SQL then you can easily translate it into a pipelined function or a function that returns a collection.

Related

PLSQL Function to sort string that's given as parameter

following issue:
I get a String as such "512, 986, 571, 665" transferred as parameter, basically a set of 3 digit numbers and these need to be returned sorted from highest to lowest.
I have only found answers pertaining such an issue when the String is found in a table and not given as parameter to a function
Here's one option: split input string into rows (that's what subquery in lines #4 - 6 does), and then aggregate them back using listagg with appropriate order by clause (line #3).
SQL> with test (col) as
2 (select '512, 986, 571, 665' from dual)
3 select listagg(val, ', ') within group (order by val) result
4 from (select to_number(trim(regexp_substr(col, '[^,]+', 1, level))) val
5 from test
6 connect by level <= regexp_count(col, ',') + 1
7 );
RESULT
--------------------------------------------------------------------------------
512, 571, 665, 986
SQL>
A programmatic approach would be to use each element in the list as the index of an associative array (so integers only, or it'll break), which will have the effect of sorting it, then loop through the resulting array constructing a new list:
create or replace function sort_number_list
( p_list varchar2 )
return varchar2
as
type aa is table of number index by pls_integer;
sort_tab aa;
sorted_list varchar2(4000);
begin
for i in 1..regexp_count(p_list, ',') +1 loop
sort_tab(regexp_substr(p_list,'[^,]+', 1, i)) := i;
end loop;
for i in indices of sort_tab loop
sorted_list := sorted_list || ', ' || i;
end loop;
return ltrim(sorted_list,', ');
end sort_number_list;
I've used the indices of syntax added in 21c which makes looping around an associative array less verbose. Alternatively you can use the first and next collection methods.
create or replace TYPE t_numlist IS table OF varchar2(32700);
create or replace function sort_str_of_num(p_string in varchar2,p_delimiter in char) return
varchar2 as
v_strnum varchar2(32767):=p_string;
v_delimiter char(1):=p_delimiter;
l_num t_numlist:=t_numlist();
incr integer :=1;
b_bool boolean:= true;
begin
v_strnum := trim(replace(v_strnum,v_delimiter,' '));
loop
l_num.extend;
l_num(incr):= regexp_substr(v_strnum,'[0-9]{1,}');
v_strnum := trim(SUBSTR(v_strnum,instr(v_strnum,' ')+1));
if (instr(v_strnum,' ')+1)=1 then
l_num.extend;
l_num(incr+1):=regexp_substr(v_strnum,'[0-9]{1,}');
exit;
end if;
incr:=incr+1;
end loop;
for i in (select column_value from table(l_num) order by to_number(column_value))loop
if b_bool then v_strnum:=i.column_value;
else v_strnum:=i.column_value||', '||v_strnum;
end if;
b_bool:=false;
end loop;
return v_strnum;
exception
when others then dbms_output.put_line('wrong entry');
end;
This will sort any list of numbers in a string and numbers can have any digit count. Also u can enter any character to be the delimiter between numbers (delimiter must be 1 character) and space character in number list string is ignored.
One way would be to use the ability of xmltable to parse a comma-separated list into XML elements, then use its getstringval() method to convert each element into a string, then finally re-aggregate the strings using listagg:
create or replace function sort_number_list
( p_list varchar2 )
return varchar2
as
sorted_list varchar2(4000);
begin
select listagg(x.column_value.getstringval(), ', ') within group (order by to_number(x.column_value.getstringval()))
into sorted_list
from xmltable(p_list) x;
return sorted_list;
end sort_number_list;
Test:
select sort_number_list('97,3,-123,4')
from dual;
SORT_NUMBER_LIST('97,3,-123,4')
------------------------------------------------------------
-123, 3, 4, 97

Find source na replacement parameters in a string and perform replacement in another string

I need a function that applies a certain rule to replace words in a string.
There are two variables:
v_imp_user_list - list of users delimited by pipe (example: JOHN|PETER|MARK|USER_PROD)
v_schema_remap_list - list of users that should be remapped (old value - new value, example: JOHN-GEORGE,USER_PROD-USER_TEST)
The function should parse v_schema_remap_list variable and if the name of the first user (before dash, old value)
exist in v_imp_user_list then replace it with the second user (after dash, new value).
Example:
v_imp_user_list := 'JOHN|PETER|MARK|USER_PROD'
v_schema_remap_list := 'JOHN-GEORGE,USER_PROD-USER_TEST'
Desired outcome:
GEORGE|PETER|MARK|USER_TEST
I have a solution which I will post but for some reason I don't like it and will appreciate any comment/review/better solution.
This function helps me to parse the v_schema_remap_list.
-- extract nth occurence in a delimited string
create or replace function f_find_str (
source_string varchar2
, occurence_outer number --occurence of the old-new pair
, occurence_inner number --old/new value, enter 1 for old or 2 for new
)
return varchar2
is
v_aux varchar2(500);
v_result varchar2(100);
begin
v_aux := ltrim(regexp_substr(',' || source_string, ',[^,]*', 1, occurence_outer),',');
if occurence_inner = 1 then
v_result := substr(v_aux, 1, instr(v_aux, '-')-1);
elsif occurence_inner = 2 then
v_result := substr(v_aux, instr(v_aux, '-')+1);
end if;
return v_result;
end;
/
This anonymous block shows my solution (see the loop inside).
declare
v_schema_remap_list varchar2(1000) := 'JOHN-GEORGE,USER_PROD-USER_TEST';
v_imp_user_list varchar2(1000) := 'JOHN|PETER|MARK|USER_PROD';
v_schema_remap_cnt number := nvl(regexp_count(v_schema_remap_list, '-'), 0);
begin
for i in 1..v_schema_remap_cnt
loop
v_imp_user_list := replace(
v_imp_user_list||'|',
f_find_str (
source_string => v_schema_remap_list
, occurence_outer => i
, occurence_inner => 1
)||'|',
f_find_str (
source_string => v_schema_remap_list
, occurence_outer => i
, occurence_inner => 2
)||'|'
);
v_imp_user_list := rtrim(v_imp_user_list, '|');
end loop;
dbms_output.put_line(v_imp_user_list); --test output
end;
/

How we can get the value from string: 1;BWQTY;4|2;NGRID;34664 and store into a variable if any specific SUBSTRING found?

I have a table name as event_creation_template which having column as TEXT ,which contains value like below :
1;BWQTY;4|2;NGRID;34664
1;DTYPE;Amount|2;LOYAL;R
How we can get the value from string:
1;BWQTY;4|2;NGRID;34664
If string is having BWQTY then store 4 into variable again search for the NGRID ,if string contains NGRID , we need in 34664 into different variable.
using Oracle PL/SQL.
considering the string like 'BWQTY' will always lie between two non-aphanumeric characters and the corresponding values like 4 lies between either non-alphanumeric character or appears at the end like in the two examples above you can use below code
CREATE OR REPLACE PR_PREP
IS
V_VAR1 VARCHAR2(100);
V_-VAR2 VARCHAR2(100);
V_SEARCH_VAL1 VARCHAR2(100) := 'BWQTY';
V_SEARCH_VAL2 VARCHAR2(100) := 'NGRID';
CURSOR C1
IS
SELECT TEXT FROM EVENT_CREATION_TEMPLATE;
BEGIN
FOR REC IN C1
LOOP
IF INSTR(REC.TEXT,V_SEARCH_VAL1) > 0 THEN
SELECT NVL(SUBSTR(SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1), 1, REGEXP_INSTR( SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1), '[^0-9a-zA-Z]',1)-1), SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL1)+LENGTH(V_SEARCH_VAL1)+1))
INTO V_VAR1
FROM DUAL ;
END IF;
IF INSTR(REC.TEXT,V_SEARCH_VAL2) > 0 THEN
SELECT NVL(SUBSTR(SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1), 1, REGEXP_INSTR( SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1), '[^0-9a-zA-Z]',1)-1), SUBSTR(REC.TEXT,INSTR(REC.TEXT,V_SEARCH_VAL2)+LENGTH(V_SEARCH_VAL2)+1))
INTO V_VAR2
FROM DUAL ;
END IF;
-- DO FURTHER WORK
END LOOP;
END;
You can use regular expressions
for BWQTY:
select str,
SUBSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'),
INSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'), ';') + 1 ,
LENGTH(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)')) - INSTR(REGEXP_SUBSTR(str, '([BWQTY]{5})([;])([0-9]+)'), ';')) AS VALUE
from test
for NGRID:
select str,
SUBSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'),
INSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'), ';') + 1 ,
LENGTH(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)')) - INSTR(REGEXP_SUBSTR(str, '([NGRID]{5})([;])([0-9]+)'), ';')) AS VALUE
from test
Where str is your input string...

Read a string and display the vowels and the consonants in it

I have as an assignment to read a string and to display the string of vowels and the string of consonants that are in that string. For example, if i have the string: aaceb, the string of vowels would be: aae and the string of consonants would be: cb. I have tried something on my own, but i have some errors (error starting in line 3 in command) and i can't figure it out why. Many thanks in advance :D
set serveroutput on
accept p_sir prompt "Sir=";
declare
v varchar2(20):=&p_sir;
vocale varchar2(20);
consoane varchar2(20);
c varchar2(1);
begin
for i in 1..length(v) loop
c:=substr(v,i,1);
case
when c='a' then vocale:=vocale||c;
when c='e' then vocale:=vocale||c;
when c='i' then vocale:=vocale||c;
when c='o' then vocale:=vocale||c;
when c='u' then vocale:=vocale||c;
else consoane:=consoane||c;
end case;
--dbms_output.put_line(c);
end loop;
dbms_output.put_line('vocale: ' ||vocale||' consoane:'||consoane);
end;
/
Proof of concept:
SQL> begin
2 dbms_output.put_line(translate('mathguy', 'zAEIOUaeiou', 'z'));
3 end;
4 /
mthgy
PL/SQL procedure successfully completed.
Use a regular expression to:
remove the non-vowels [^aeiou] to be left with only the vowels.
remove the vowels [aeiou] and non-alphabetic characters [^a-z] to be left with only the consonants.
Like this:
DECLARE
v_text VARCHAR2(200) := 'Your text string';
BEGIN
DBMS_OUTPUT.PUT_LINE(
'Vowels: ' || REGEX_REPLACE( v_text, '[^aeiou]', NULL, 1, 0, 'i' )
);
DBMS_OUTPUT.PUT_LINE(
'Consonants: ' || REGEX_REPLACE( v_text, '[aeiou]|[^a-z]', NULL, 1, 0, 'i' )
);
END;
/

Multiple REPLACE function in Oracle

I am using the REPLACE function in oracle to replace values in my string like;
SELECT REPLACE('THE NEW VALUE IS #VAL1#','#VAL1#','55') from dual
So this is OK to replace one value, but what about 20+, should I use 20+ REPLACE function or is there a more practical solution.
All ideas are welcome.
Even if this thread is old is the first on Google, so I'll post an Oracle equivalent to the function implemented here, using regular expressions.
Is fairly faster than nested replace(), and much cleaner.
To replace strings 'a','b','c' with 'd' in a string column from a given table
select regexp_replace(string_col,'a|b|c','d') from given_table
It is nothing else than a regular expression for several static patterns with 'or' operator.
Beware of regexp special characters!
The accepted answer to how to replace multiple strings together in Oracle suggests using nested REPLACE statements, and I don't think there is a better way.
If you are going to make heavy use of this, you could consider writing your own function:
CREATE TYPE t_text IS TABLE OF VARCHAR2(256);
CREATE FUNCTION multiple_replace(
in_text IN VARCHAR2, in_old IN t_text, in_new IN t_text
)
RETURN VARCHAR2
AS
v_result VARCHAR2(32767);
BEGIN
IF( in_old.COUNT <> in_new.COUNT ) THEN
RETURN in_text;
END IF;
v_result := in_text;
FOR i IN 1 .. in_old.COUNT LOOP
v_result := REPLACE( v_result, in_old(i), in_new(i) );
END LOOP;
RETURN v_result;
END;
and then use it like this:
SELECT multiple_replace( 'This is #VAL1# with some #VAL2# to #VAL3#',
NEW t_text( '#VAL1#', '#VAL2#', '#VAL3#' ),
NEW t_text( 'text', 'tokens', 'replace' )
)
FROM dual
This is text with some tokens to replace
If all of your tokens have the same format ('#VAL' || i || '#'), you could omit parameter in_old and use your loop-counter instead.
Bear in mind the consequences
SELECT REPLACE(REPLACE('TEST123','123','456'),'45','89') FROM DUAL;
will replace the 123 with 456, then find that it can replace the 45 with 89.
For a function that had an equivalent result, it would have to duplicate the precedence (ie replacing the strings in the same order).
Similarly, taking a string 'ABCDEF', and instructing it to replace 'ABC' with '123' and 'CDE' with 'xyz' would still have to account for a precedence to determine whether it went to '123EF' or ABxyzF'.
In short, it would be difficult to come up with anything generic that would be simpler than a nested REPLACE (though something that was more of a sprintf style function would be a useful addition).
In case all your source and replacement strings are just one character long, you can simply use the TRANSLATE function:
SELECT translate('THIS IS UPPERCASE', 'THISUP', 'thisup')
FROM DUAL
See the Oracle documentation for details.
This is an old post, but I ended up using Peter Lang's thoughts, and did a similar, but yet different approach. Here is what I did:
CREATE OR REPLACE FUNCTION multi_replace(
pString IN VARCHAR2
,pReplacePattern IN VARCHAR2
) RETURN VARCHAR2 IS
iCount INTEGER;
vResult VARCHAR2(1000);
vRule VARCHAR2(100);
vOldStr VARCHAR2(50);
vNewStr VARCHAR2(50);
BEGIN
iCount := 0;
vResult := pString;
LOOP
iCount := iCount + 1;
-- Step # 1: Pick out the replacement rules
vRule := REGEXP_SUBSTR(pReplacePattern, '[^/]+', 1, iCount);
-- Step # 2: Pick out the old and new string from the rule
vOldStr := REGEXP_SUBSTR(vRule, '[^=]+', 1, 1);
vNewStr := REGEXP_SUBSTR(vRule, '[^=]+', 1, 2);
-- Step # 3: Do the replacement
vResult := REPLACE(vResult, vOldStr, vNewStr);
EXIT WHEN vRule IS NULL;
END LOOP;
RETURN vResult;
END multi_replace;
Then I can use it like this:
SELECT multi_replace(
'This is a test string with a #, a $ character, and finally a & character'
,'#=%23/$=%24/&=%25'
)
FROM dual
This makes it so that I can can any character/string with any character/string.
I wrote a post about this on my blog.
I have created a general multi replace string Oracle function by a table of varchar2 as parameter.
The varchar will be replaced for the position rownum value of table.
For example:
Text: Hello {0}, this is a {2} for {1}
Parameters: TABLE('world','all','message')
Returns:
Hello world, this is a message for all.
You must create a type:
CREATE OR REPLACE TYPE "TBL_VARCHAR2" IS TABLE OF VARCHAR2(250);
The funcion is:
CREATE OR REPLACE FUNCTION FN_REPLACETEXT(
pText IN VARCHAR2,
pPar IN TBL_VARCHAR2
) RETURN VARCHAR2
IS
vText VARCHAR2(32767);
vPos INT;
vValue VARCHAR2(250);
CURSOR cuParameter(POS INT) IS
SELECT VAL
FROM
(
SELECT VAL, ROWNUM AS RN
FROM (
SELECT COLUMN_VALUE VAL
FROM TABLE(pPar)
)
)
WHERE RN=POS+1;
BEGIN
vText := pText;
FOR i IN 1..REGEXP_COUNT(pText, '[{][0-9]+[}]') LOOP
vPos := TO_NUMBER(SUBSTR(REGEXP_SUBSTR(pText, '[{][0-9]+[}]',1,i),2, LENGTH(REGEXP_SUBSTR(pText, '[{][0-9]+[}]',1,i)) - 2));
OPEN cuParameter(vPos);
FETCH cuParameter INTO vValue;
IF cuParameter%FOUND THEN
vText := REPLACE(vText, REGEXP_SUBSTR(pText, '[{][0-9]+[}]',1,i), vValue);
END IF;
CLOSE cuParameter;
END LOOP;
RETURN vText;
EXCEPTION
WHEN OTHERS
THEN
RETURN pText;
END FN_REPLACETEXT;
/
Usage:
TEXT_RETURNED := FN_REPLACETEXT('Hello {0}, this is a {2} for {1}', TBL_VARCHAR2('world','all','message'));
Thanks for the answer. Rather than specifying the translation in the call, you can also do this using a cursor as shown below.
create or replace function character_substitutions (input_str varchar2)
return varchar2
as
v_result VARCHAR2(4000);
cursor c_translate_table is
select '&' as symbol_to_replace, 'amp' as symbol_in_return_string from dual
union all
select '/' as symbol_to_replace, '_' as symbol_in_return_string from dual
union all
select '"' as symbol_to_replace, 'in' as symbol_in_return_string from dual
union all
select '%' as symbol_to_replace, 'per' as symbol_in_return_string from dual
union all
select '.' as symbol_to_replace, '_' as symbol_in_return_string from dual;
begin
v_result := input_str;
for r_translate in c_translate_table loop
v_result := REPLACE( v_result, r_translate.symbol_to_replace, r_translate.symbol_in_return_string);
end loop;
return v_result;
end;
/
Another example of how to apply an ordered list of character replacements. In this case:
convert a backslash to a slash
convert a double slash occurrence to a single slash
convert a single quote to an escaped single quote
For example, convert from:
\\server\Folder\611891\Joe's Doc.pdf
to
/server/Folder/611891/Joe\'s Doc.pdf
Can use a recursive CTE to iterate through the ordered list in sequence. First part demonstrates how the string evolves:
WITH subs AS (
SELECT '\' convert_from, '/' convert_to, 1 seq FROM DUAL
UNION SELECT '//', '/', 2 FROM DUAL
UNION SELECT '''', '\''', 3 FROM DUAL
),
rcte(convert_from, convert_to, seq, src) AS (
SELECT '', '', 0, '\\server\Folder\611891\Joe''s Doc.pdf' src FROM DUAL
UNION ALL
SELECT s.convert_from, s.convert_to, s.seq, REPLACE(r.src, s.convert_from, s.convert_to)
FROM rcte r
JOIN subs s ON (s.seq = r.seq + 1)
)
SELECT *
FROM rcte
ORDER BY seq
;
| CONVERT_FROM | CONVERT_TO | SEQ | SRC |
|--------------|------------|-----|--------------------------------------|
| (null) | (null) | 0 | \\server\Folder\611891\Joe's Doc.pdf |
| \ | / | 1 | //server/Folder/611891/Joe's Doc.pdf |
| // | / | 2 | /server/Folder/611891/Joe's Doc.pdf |
| ' | \' | 3 | /server/Folder/611891/Joe\'s Doc.pdf |
But actually only interested in the final result:
SELECT src
FROM rcte
WHERE seq = (SELECT MAX(seq) FROM subs)
;
| SRC |
|--------------------------------------|
| /server/Folder/611891/Joe\'s Doc.pdf |

Resources