split a string into individual elements in PL/SQL - oracle

This is for PL/SQL
I am being passed in a series of strings, delimited by a comma, in a string.
I would like to take those delimited strings and create an IN clause within my query.
I was thinking a simply add an open parenthesis and single quote to the front and, at the end of the string and single quote with a closing parenthesis, trim the spaces, and swap the commas out with a quote comma quote string. the problem falls when the string has an embedded space in the delimited string.
For example, the string that is being passed in is
TNR, abc, D N, w
What i am looking to receive after processing it would be
('TNR','ABC','D N','W')
I'm not sure where i should begin.

Use REPLACE to change , into ', ' and then prepend (' and append '):
DECLARE
list VARCHAR2(50) := 'TNR, abc, D N, w';
BEGIN
list := '(''' || REPLACE(list, ', ', ''', ''') || ''')';
DBMS_OUTPUT.PUT_LINE(list);
END;
/
If the white space after the comma is optional then you can use REGEXP_REPLACE rather than REPLACE:
DECLARE
list VARCHAR2(50) := 'TNR,abc, D N, w';
BEGIN
list := '(''' || REGEXP_REPLACE(list, ',\s*', ''', ''') || ''')';
DBMS_OUTPUT.PUT_LINE(list);
END;
/
Which both output:
('TNR', 'abc', 'D N', 'w')
db<>fiddle here

What you described is way too complicated. Just split the string into rows and use them in an IN clause as a subquery.
This is example that runs in SQL*Plus (hence single quotes and && for the substitution variable); query returns one row (from the dual table) as its dummy column contains letter X which is part of the input string:
SQL> SELECT *
2 FROM DUAL
3 WHERE dummy IN ( SELECT TRIM (REGEXP_SUBSTR ('&&par_string',
4 '[^,]+',
5 1,
6 LEVEL))
7 FROM DUAL
8 CONNECT BY LEVEL <= REGEXP_COUNT ('&&par_string', ',') + 1);
Enter value for par_string: TNR, abc, D N,X, w
D
-
X
SQL>
Subquery itself returns
SQL> SELECT TRIM (REGEXP_SUBSTR ('&&par_string',
2 '[^,]+',
3 1,
4 LEVEL))
5 FROM DUAL
6 CONNECT BY LEVEL <= REGEXP_COUNT ('&&par_string', ',') + 1;
Enter value for par_string: TNR, abc, D N,X, w
TRIM(REGEXP_SUBSTR
------------------
TNR
abc
D N
X
w
SQL>
If you use bind variable, you'd use e.g. :par_string instead of '&&par_string'; or, if it was passed to a procedure or a function, you'd just just the parameter name - par_string.

Related

Flag duplicate characters in a string

I'm using oracle apex and i'm trying to write a pl/sql statement that will flag duplicates in a string. For example the string 'P,T,P,C' has two occurrences of the letter 'P' so it should raise an error. After all my digging the closest I got to achieving this was by using REGEXP_LIKE, but my regular expression skills are sub par. If anyone can assist that would be much appreciated.
DECLARE
v_seccode varchar2(10) := 'P,T,P,C';
BEGIN
if regexp_like(v_seccode, '(P{2,}?|C{2,}?|W{2,}?|T{2,}?)') then
raise_application_error(-20001,'You can not have the same SEC code listed more than once!');
end if;
END;
If your apex version is relatively recent you have access to the APEX_STRING API. Here is some code using that API, using same logic #Littlefoot showed in the other answer:
DECLARE
l_arr1 apex_t_varchar2;
l_arr2 apex_t_varchar2;
BEGIN
-- convert string to array with "," delimiter
l_arr1 := apex_string.split(:P1_SECCODE,',');
l_arr2 := l_arr1;
-- remove dupes from l_arr2
l_arr2 := l_arr2 MULTISET UNION DISTINCT l_arr2;
IF l_arr2 != l_arr1 THEN
return 'You can not have the same SEC code listed more than once!';
END IF;
END;
/
As you're using Apex, I suggest you create a validation within it. Code you posted suggests that you'd want to handle that elsewhere (process? Database trigger?).
Presuming that page item name is P1_SECCODE, validation - a PL/SQL function that returns error text - might look like this (read comments within code):
declare
l_seccode varchar2(20);
begin
-- Split P1_SECCODE into rows, fetch distinct values and aggregate them back.
-- If P1_SECCODE = 'P,T,P,C' then the L_SECCODE = 'P,T,C'
select listagg(distinct regexp_substr(:P1_SECCODE, '[^,]+', 1, level), ',')
within group (order by null)
into l_seccode
from dual
connect by level <= regexp_count(:P1_SECCODE, ',') + 1;
-- Now compare whether P1_SECCODE has the same number of commas as L_SECCODE.
-- If not, it means that P1_SECCODE contained duplicates so - return an error message
if regexp_count(:P1_SECCODE, ',') <> regexp_count(l_seccode, ',') then
return 'You can not have the same SEC code listed more than once!';
end if;
end;
If your database version doesn't support distinct within listagg, then first fetch distinct values and then aggregate them:
declare
l_seccode varchar2(20);
begin
-- Split P1_SECCODE into rows, fetch distinct values and aggregate them back.
-- If P1_SECCODE = 'P,T,P,C' then the L_SECCODE = 'P,T,C'
with temp as
(select distinct regexp_substr(:P1_SECCODE, '[^,]+', 1, level), ',') val
from dual
connect by level <= regexp_count(:P1_SECCODE, ',') + 1
)
select listagg(val, ',') within group (order by null)
into l_seccode
from temp;
-- Now compare whether P1_SECCODE has the same number of commas as L_SECCODE.
-- If not, it means that P1_SECCODE contained duplicates so - return an error message
if regexp_count(:P1_SECCODE, ',') <> regexp_count(l_seccode, ',') then
return 'You can not have the same SEC code listed more than once!';
end if;
end;
Given that your string can only have 4 possible letters PCWT, you could just use LIKE expressions along with a regular query:
SELECT *
FROM yourTable
WHERE string NOT LIKE '%P%P%' AND
string NOT LIKE '%C%C%' AND
string NOT LIKE '%W%W%' AND
string NOT LIKE '%T%T%';

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

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...

oracle dynamic validation

Consider a table with 3 columns
Value1   operator     value2
abc          =               xyz
1            !=               2
5            >                8
9            <=              11
xyz          is not Null
{null val}   is null
I want to write a genric function which returns the validated result either true or false
by validating value1 with value2 using the operator
or check value1 in case of 'is null' and 'is not null' operators (Last two cases)
You could do something like this with dynamic SQL (note that in reality you'd need to add a bunch of logic to prevent SQL injection attacks)
SQL> ed
Wrote file afiedt.buf
1 create or replace function evaluate( p_val1 in varchar2,
2 p_op in varchar2,
3 p_val2 in varchar2 )
4 return varchar2
5 is
6 l_result varchar2(1);
7 l_sql_stmt varchar2(1000);
8 begin
9 if( p_val2 is not null )
10 then
11 l_sql_stmt := 'select (case when :1 ' || p_op || ' :2 then ''Y'' else ''N'' end) from dual';
12 execute immediate l_sql_stmt
13 into l_result
14 using p_val1, p_val2;
15 else
16 l_sql_stmt := 'select (case when :1 ' || p_op || ' then ''Y'' else ''N'' end) from dual';
17 execute immediate l_sql_stmt
18 into l_result
19 using p_val1;
20 end if;
21 return l_result;
22* end;
SQL> /
Function created.
SQL> select evaluate( 'xyz', 'is not null', null )
2 from dual;
EVALUATE('XYZ','ISNOTNULL',NULL)
--------------------------------------------------------------------------------
Y
SQL> select evaluate( 'abc', '=', 'xyz' )
2 from dual;
EVALUATE('ABC','=','XYZ')
--------------------------------------------------------------------------------
N
Since you are storing the data in a table, that implies that each column is a VARCHAR2. My guess, though, is that you don't always want to use string comparison semantics. For example, the string '9' is greater than the string '11' while the number 9 is less than the number 11. If you want to use something other than string comparison semantics, you'd need to add code that inspected the parameters and applied whatever logic you would like to determine what comparison semantics you want to apply and then generated the appropriate dynamic SQL statement.
I would strongly question the wisdom of the requirement, however. First off, it makes little sense from a data model standpoint to store numeric data in a VARCHAR2 column-- when you find yourself trying to mix string and numeric data in the same column, you've almost always made a data model mistake. I would also be hard-pressed to imagine what business problem you are trying to solve that would involve this sort of dynamic function-- it seems likely that there is a better way to solve whatever problem you are attempting to solve.
It seems to me you can escape dynamic SQL with unwinding logic in a list of rules.
If you need to check one record use something like
with CheckedTable as (select * from t where primary_key = ID)
select count(*) from
(
select * from CheckedTable where operator = '=' and value1 = value2
union all
select * from CheckedTable where operator = '!=' and value1 <> value2
union all
select * from CheckedTable where operator = '>' and value1 > value2
union all
...
select * from CheckedTable where operator = 'is null' and value1 is null
)
So 1 = TRUE and 0 = FALSE.
P.S. If you want to support IN clause you can use this workaround (remove brackets from value2!)
select * from CheckedTable where operator = 'in' and ',' || value2 || ',' like '%,' || value1 || ',%'
proof on SQLFiddle
looking through above question and the results, i came up with some points that we should take care of using string and comparisons:
----->
its possible to compare numeric values with any comparison function ,ie;
=, !=, <, >, <=, >=, IS NULL, LIKE, BETWEEN, IN
but beware of using quotes;
SELECT (CASE WHEN '9' > '12' THEN 'Y' ELSE 'N' END) FROM dual;-- results Y
because, here the implicit positional comparison is done by checking 9>1 (1 in the 12), so the condition 9>1 is true, so results as Y.
The correct way of comparison with numeric is without quotes:
SELECT (CASE WHEN 9 > 12 THEN 'Y' ELSE 'N' END) FROM dual;--results N
-----> its meaningless to compare a string with another with '>' and '<'
-----> we've UTL_MATCH package especially for string matching.
SELECT utl_match.edit_distance( 'abc','abcde' ) FROM dual;--results 2, shows no:of edits required to make string1 to string2.
SELECT UTL_MATCH.EDIT_DISTANCE_similarity('abc', 'abcde') FROM DUAL;-- results 60, shows the similarity percentage of two strings.

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