Oracle: Get length of partial string match - string

Imagine I have a table like
Name
----
ABCDEFG
ABChello world
ABCDEfoo
ABbar
ABCDEF
ABCDEFGHIJKLMNOP
zzz
qABCD
ABCqqqGH
ABCABC
I want to do a query and figure out how many characters of each string match the desired string "ABCDEFGHIJ," always starting from the beginning. That is...
Name MatchingLength
---- ----
ABCDEFG 7
ABChello world 3
ABCDEzoo 5
ABbar 2
ABCDEF 6
ABCDEFGHIJKLMNOP 10
zzz 0
qABCD 0
ABCqqqGH 3
ABCABC 3
Is there any way to do this cleanly in Oracle? I'm at a loss.

Don't know about "clean", but here are two solutions.
-- The hardcoded, bad performance. No transformation of your string though.
with patterns as (
select substr('ABCDEFGHIJ', 1, rownum) txt
from dual
connect by level <= length('ABCDEFGHIJ')
)
select d.txt, coalesce(max(length(p.txt)), 0)
from dummy d
left join patterns p
on instr(d.txt, p.txt) = 1
group by d.txt
order by 2 desc;
-- The cool one with regex.
-- Though transforming your input string,
-- this can also be done with ease making something that transorms it for you
-- like in the previous example, more complicated task than the previous,
-- as oracle sucks with string manipulation. You can however write it in java.
select d.txt, coalesce(LENGTH(REGEXP_SUBSTR(d.txt, '^A(B(C(D(E(F(G(H(I(J)?)?)?)?)?)?)?)?)')), 0)
from dummy d;
http://www.sqlfiddle.com/#!4/85ba6/23
UPDATE
with patterns as (
select substr('ABCDEFGHIJ', 1, rownum) txt
from dual
connect by level <= length('ABCDEFGHIJ')
)
select d.txt, coalesce(max(length(p.txt)), 0)
from dummy d
left join patterns p
on instr(d.txt, p.txt) = 1
where d.txt LIKE substr('ABCDEFGHIJ', 1, 1) || '%'
group by d.txt
order by 2 desc;
Updated fiddle: http://www.sqlfiddle.com/#!4/37400/6
Query plan produced tested on oracle 10g
SELECT STATEMENT, GOAL = ALL_ROWS
SORT ORDER BY
SORT GROUP BY NOSORT
NESTED LOOPS OUTER
INDEX RANGE SCAN I <<<< Uses the index.
VIEW
COUNT
CONNECT BY WITHOUT FILTERING
FAST DUAL

Assuming that you will want to have the matching count of only those strings which are starting with
ABCDEFGHIJ and for the strings like qABCD the matching count will be 0
SELECT STR,DECODE(SUBSTR(STR,1,1),'A',LENGTH(STR)-
NVL(LENGTH(REPLACE(TRANSLATE(STR,'ABCDEFGHIJ',' '),' ','')),0),0) MATCHING_LENGTH FROM table

If you're using Oracle 11gR2 then you can use Recursive Common Table Expressions like this:
with rcte(txt,t, p, c) as
(
select d.txt , d.txt t, 'ABCDEFGHIJ' p, 0 c
from dummy d
union all
select txt ,substr(t, 2), substr(p, 2), case when substr(t, 1, 1) = substr(p, 1, 1) then 1 else 0 end
from rcte
where length(t) > 0 and length(p) > 0 and substr(t, 1, 1) = substr(p, 1, 1)
)
select txt, sum(c) from rcte
group by txt;
Here is a sqlfiddle demo (thanks to #Roger)

Assuming the matching criteria is both symbol and position equality (value of pattern ABCqqqGH is 5) you might try this:
17:57:03 SYSTEM#sandbox> #sf test
VAL
------------------------------
ABCDEFG
ABChello world
ABCDEzoo
ABbar
ABCDEF
ABCDEFGHIJKLMNOP
zzz
qABCD
8 rows selected.
Elapsed: 00:00:00.01
17:57:05 SYSTEM#sandbox> #get match
1 select t.val, count(l)
2 from test t
3 left join (select level l from dual connect by level <= length('ABCDEFGHIJ')) i
4 on substr(t.val, i.l, 1) = substr('ABCDEFGHIJ', i.l, 1)
5 group by t.val
6* order by 2 desc
17:57:07 SYSTEM#sandbox> /
VAL COUNT(L)
------------------------------ ----------
ABCDEFGHIJKLMNOP 10
ABCDEFG 7
ABCDEF 6
ABCDEzoo 5
ABChello world 3
ABbar 2
zzz 0
qABCD 0
8 rows selected.
Elapsed: 00:00:00.02

declare
v_1 number := 0;
v_pattern VARCHAR(26) := '&n';
v_f number;
v_spaces VARCHAR(30) := ' ';
v_l number;
v_c varchar(20) := ' ';
v_n varchar(20) := ' ';
BEGIN
v_f := Ascii(SubStr(v_pattern,1,1));
v_l := Ascii(SubStr(v_pattern,Length(v_pattern)));
v_spaces := LPad(' ',Length(v_pattern),' ');
for i in (select str,TRANSLATE(REPLACE(str,' ',''),v_pattern,v_spaces) c1,length(REPLACE(str,' ',''))-nvl(length(replace(TRANSLATE(REPLACE(str,' ',''),v_pattern,v_spaces),' ','')),0) c2 from table
where ascii(substr(str,1,1)) IN (SELECT DISTINCT Ascii(SubStr(v_pattern,LEVEL,1)) FROM dual CONNECT BY LEVEL<=Length(v_pattern))) loop
for j in 1..i.c2 loop
v_c :=instr(i.c1,' ',1,j);
v_n :=instr(i.c1,' ',1,j+1);
if v_c+1=v_n then
v_1 := v_1+1;
end if;
end loop;
if(v_1+1 = i.c2) then
dbms_output.put_line('String : '||i.str||' and Matching count : '||i.c2);
else
dbms_output.put_line('String : '||i.str||' and Matching count : '||((v_1)-1));
end if;
v_1 := 0;
end loop;
FOR k IN (SELECT str FROM table WHERE NOT(Ascii(substr(str,1,1)) IN (SELECT DISTINCT Ascii(SubStr(v_pattern,LEVEL,1)) FROM dual CONNECT BY LEVEL<=Length(v_pattern)))) LOOP
dbms_output.put_line('String : '||k.str||' and Matching count : '||v_1);
END LOOP;
end;

I hope the following helps:
CREATE TABLE TESTME ( TNAME VARCHAR2(30));
INSERT INTO TESTME VALUES('ABCDEFG');
INSERT INTO TESTME VALUES('ABChello world');
INSERT INTO TESTME VALUES('ABCDEzoo');
INSERT INTO TESTME VALUES('ABbar');
INSERT INTO TESTME VALUES('ABCDEF');
INSERT INTO TESTME VALUES('ABCDEFGHIJKLMNOP');
INSERT INTO TESTME VALUES('zzz');
INSERT INTO TESTME VALUES('qABCD');
CREATE OR REPLACE FUNCTION GET_MLENGTH( P_INPUT VARCHAR2)
RETURN NUMBER
IS
-- COMBARING STRING
C VARCHAR2(10) := ('ABCDEFGHIJ');
N NUMBER := 0;
BEGIN
FOR I IN 1..LENGTH(P_INPUT) LOOP
IF SUBSTR(P_INPUT,I,1) = SUBSTR(C,I,1) THEN
N := N + 1;
ELSE
RETURN N;
END IF;
END LOOP;
RETURN N;
END;
/
SELECT TNAME , GET_MLENGTH(TNAME) FROM TESTME ;
TNAME GET_MLENGTH(TNAME)
------------------------------ ------------------
ABCDEFG 7
ABChello world 3
ABCDEzoo 5
ABbar 2
ABCDEF 6
ABCDEFGHIJKLMNOP 10
zzz 0
qABCD 0

Related

Oracle REGEXP_REPLACE string to replace 'n' times starting with nn position

I want to replace '|' with '_'. The replacement should start from nnth character and replace n times. For e.g.
ABC|1234|mno|p|q|r|456|XYZ|QRS|TUV ====> ABC|1234|mno|p_q_r|456|XYZ|QRS|TUV
In above example nn=14 and n=3
So far, I've tried this but not getting the expected results
SELECT REGEXP_REPLACE('ABC|1234|mno|p|q|r|456|XYZ', '[|]', '_',14) rep_str FROM DUAL
In your simple example it's easier to specify both 2 occurences:
regexp_replace(
str
, '\|([^|]+)'
||'\|([^|]+)' -- 2 times just to make it more readable
||'(.*)' -- others
,'_\1_\2\3'
,14
)
Full example with test data: DBFiddle
with t as (
select
'ABC|1234|mno|p|q|r|456|XYZ|QRS|TUV' str
,'ABC|1234|mno|p_q_r|456|XYZ|QRS|TUV' chk
from dual
)
select
str,chk,
regexp_replace(
str
, '\|([^|]+)'
||'\|([^|]+)' -- 2 times just to make it more readable
||'(.*)' -- others
,'_\1_\2\3'
,14
) as str2
from t
/
Or if you make it more customizable and specify number of replacement easier, you can use simple inline pl/sql function with a loop like this: DBFiddle
with function regexp_replaces(
source_char varchar2
,pattern varchar2
,replace_char varchar2
,position int
,cnt int
) return varchar2
as
res varchar2(4000):=source_char;
begin
for i in 1..cnt loop
res:=regexp_replace(res,pattern,replace_char,position,1);
end loop;
return res;
end;
select
str,chk,
regexp_replaces(str,'\|','_',14,2) as str2
from t;
You can do it with plain substr/instr, but need to process edge cases carefully. Extract the part you need and replace all pipes in it. Then put everything together back.
with
--
function replace_n(
str in varchar2,
start_ in number,
count_ in number
)
return varchar2
as
begin
return
/*Starting part unchanged*/
substr(str, 1, start_)
/*Replacement: locate n'th - 1 occurrence of pipe*/
|| translate(
substr(str, start_ + 1, instr(str, '|', start_, count_-1) - start_)
, '|'
, '_'
)
/*Remaining string*/
|| substr(str, instr(str, '|', start_, count_ - 1) + 1)
;
end;
--
a(a) as (
select
'ABC|1234|mno|p|q|r|456|XYZ|QRS|TUV'
from dual
)
select replace_n(a, 14, 3) as res
from a
| RES |
| :--------------------------------- |
| ABC|1234|mno|p_q_r|456|XYZ|QRS|TUV |
db<>fiddle here
UPD: Or if you were about replacement in the substring of size n starting from position nnn:
with
--
function replace_n(
str in varchar2,
start_ in number,
count_ in number
)
return varchar2
as
begin
return
/*Starting part unchanged*/
substr(str, 1, start_)
/*Replacement: extract substring on size n*/
|| translate(
substr(str, start_ + 1, instr(str, '|', start_, count_-1) - start_)
, '|'
, '_'
)
/*Remaining string*/
|| substr(str, instr(str, '|', start_, count_ - 1) + 1)
;
end;
--
db<>fiddle here

Removing white spaces between numbers using regex in abinitio if length is 9 by excluding spaces

For example ,
If My Input is : some text here 345 646 356 some text 235 5343
Output should be : some text here 345646356 some text 235 5343
In this case, it needs to remove spaces between numbers if length is 9. otherwise spaces should be as it is .
I have tried below command but it removes all the spaces between numbers if length is <9 or >9.
Input : my data is 345 245 254 and 454 356 34 and
Logic :
final_value = re_replace( final_value , "((?<=\d) +(?=\d))" ,"");
Output :
my data is 345245254 and 45435634 and
But I would need output as
my data is 345245254 and 454 356 34 and
This is long but it works, assuming the string does not contain \x00 characters:
out::clean_9_digits(instr)=
begin
let string(int)[int] strv=string_split_no_empty(instr, " "); // length-prefixed to mark removed elements with "\0"
let int i=0;
while (i<length_of(strv))
if ( not string_is_numeric(strv[i]))
i = i + 1; // continue
else
begin
let string(int) thisnum = strv[i];
let int j = i + 1;
while (j < length_of(strv) && string_is_numeric(strv[j]) )
begin // concatenate all following numeric elements
thisnum = string_concat(thisnum,strv[j]);
j=j+1;
end;
if (length_of(thisnum) == 9) // match!
begin
strv[i]=thisnum; // replace first element with the 9 digit number
j = i + 1; // mark remaining numeric elements that were combined
while (j < length_of(strv) && string_is_numeric(strv[j]) )
begin
strv[j]="\0";
j = j + 1;
end;
end;
i=j+1; // continue at next element following numeric elements
end;
out :: string_replace(string_join(strv, " "), "\0 ", "");
end;
/*Reformat operation*/
out::reformat(in)=
begin
out.instr :: in.instr;
out.outstr :: clean_9_digits(in.instr);
end;
Try with this REGEXP (edited):
re_match_replace_all( str=in.final_value,
pattern="(\\D\\s*\\d)\\s*(\\d)\\s*(\\d)\\s*(\\d)\\s*(\\d)\\s*(\\d)\\s*(\\d)\\s*(\\d)\\s*(\\d\\s*\\D)",
replace_str="$1$2$3$4$5$6$7$8$9"
)
(This does not solve the problem in all cases, see comments)

PLSQL - Concatenate two strings and split into 3 strings on condition

In PL-SQL, I want to concatenate two strings taken from 2 columns (address line 1, address line 2, max 45 characters each) into 3 strings (address line 1, address line 2, address line 3, maximum 34 characters each) based on the condition that no word should cut in the middle. for example:
If address line 1 contains:
1, abc park, def chowk, ghi marg c-123 street
and address line 2 contains:
city mumbai, pin - 435353
Combined with numbering to show where 34 characters falls:
1111111111222222222233333 1111111111222222222233333
12345678901234567890123456789012341234567890123456789012345678901234123
1, abc park, def chowk, ghi marg c-123 street city mumbai, pin - 435353
The result should be like this
Add1 (max 34 char):
1, abc park, def chowk, ghi marg
Add2 (max 34 char):
c-123 street city mumbai,
Add3 (max 34 char):
pin - 435353
I had the same problem, I have written this function which split a text into fixed length lines without truncating words.
pi_text : Your unwrapped text
pi_max_line : Line length you want to split
CREATE OR REPLACE FUNCTION wrap_to_paragraph(pi_text VARCHAR2,
pi_max_line PLS_INTEGER,
pi_end_paragraph VARCHAR2 DEFAULT CHR(10)) RETURN VARCHAR2 IS
TYPE paragraph_tabletype_aat IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
l_loc_para paragraph_tabletype_aat;
l_loc_lines INTEGER;
l_return VARCHAR2(32767);
PROCEDURE to_paragraph(pi_text_in IN VARCHAR2,
pi_line_length IN INTEGER,
po_paragraph_out IN OUT paragraph_tabletype_aat,
pio_num_lines_out IN OUT INTEGER,
pi_word_break_at_in IN VARCHAR2 := ' ') IS
l_len_text INTEGER := LENGTH(pi_text_in);
l_line_start_loc INTEGER := 1;
l_line_end_loc INTEGER := 1;
l_last_space_loc INTEGER;
l_curr_line VARCHAR2(100);
l_replace_string VARCHAR2(100) := NULL;
PROCEDURE set_replace_string IS
BEGIN
l_replace_string := RPAD('#', LENGTH(pi_word_break_at_in), '#');
END set_replace_string;
PROCEDURE find_last_delim_loc(pi_line_in IN VARCHAR2,
po_loc_out OUT INTEGER) IS
l_line VARCHAR2(1000) := pi_line_in;
BEGIN
IF pi_word_break_at_in IS NOT NULL
THEN
l_line := translate(pi_line_in, pi_word_break_at_in, l_replace_string);
END IF;
po_loc_out := INSTR(l_line, '#', -1);
END find_last_delim_loc;
BEGIN
set_replace_string;
IF l_len_text IS NULL
THEN
pio_num_lines_out := 0;
ELSE
pio_num_lines_out := 1;
LOOP
EXIT WHEN l_line_end_loc > l_len_text;
l_line_end_loc := LEAST(l_line_end_loc + pi_line_length, l_len_text + 1);
/* get the next possible line of text */
l_curr_line := SUBSTRB(pi_text_in || ' ', l_line_start_loc, pi_line_length + 1);
/* find the last space in this section of the line */
find_last_delim_loc(l_curr_line, l_last_space_loc);
/* When NO spaces exist, use the full current line*/
/* otherwise, cut the line at the space. */
IF l_last_space_loc > 0
THEN
l_line_end_loc := l_line_start_loc + l_last_space_loc;
END IF;
IF INSTR(l_curr_line, pi_end_paragraph) > 0
THEN
l_line_end_loc := l_line_start_loc + INSTR(l_curr_line, pi_end_paragraph) + 1;
END IF;
/* Add this line to the paragraph */
po_paragraph_out(pio_num_lines_out) := REPLACE(SUBSTRB(pi_text_in,
l_line_start_loc,
l_line_end_loc - l_line_start_loc),
pi_end_paragraph);
pio_num_lines_out := pio_num_lines_out + 1;
l_line_start_loc := l_line_end_loc;
END LOOP;
pio_num_lines_out := pio_num_lines_out - 1;
END IF;
END to_paragraph;
BEGIN
/* Return original */
IF (pi_max_line = 0 OR pi_max_line > 99)
THEN
RETURN pi_text;
END IF;
/* Build each paragraph in record */
to_paragraph(pi_text, pi_max_line, l_loc_para, l_loc_lines);
/* Extract Result */
FOR i IN 1 .. l_loc_lines
LOOP
l_return := l_return || l_loc_para(i) || pi_end_paragraph;
END LOOP;
RETURN TRIM(CHR(10) FROM l_return);
END wrap_to_paragraph;
REGEXP_SUBSTR() can be used to solve this for you. As I said in my comment to your original post, your text says don't break in a word but your example shows ADD3 breaking after the last comma/space, not just space so you need to define your rule further (Maybe when it's the last section after the last comma only?). Anyway, sticking with what you wrote, the regex gives either the 1st, 2nd or 3rd occurrence of up to 34 characters that are followed by a character that is not a whitespace, followed by a whitespace character or the end of the line.
SQL> with tbl(addr) as (
select '1, abc park, def chowk, ghi marg c-123 street city mumbai, pin - 435353'
from dual
)
select regexp_substr(addr, '(.{0,34}\S)(\s|$)', 1, 1) add1,
regexp_substr(addr, '(.{0,34}\S)(\s|$)', 1, 2) add2,
regexp_substr(addr, '(.{0,34}\S)(\s|$)', 1, 3) add3
from tbl;
ADD1 ADD2 ADD3
--------------------------------- -------------------------------- ------
1, abc park, def chowk, ghi marg c-123 street city mumbai, pin - 435353
SQL>

ORACLE PL-SQL How to SPLIT a string and RETURN the list using a Function

How to Split the given String for the given Delimiter.
Ex:
INPUT
String => '1,2,3,4,5'
Delimiter => ','
OUTPUT
1
2
3
4
5
What about this? The regular expression allows for null list elements too.
SQL> with tbl(str) as (
2 select '1,2,,4,5' from dual
3 )
4 select regexp_substr(str, '(.*?)(,|$)', 1, level, null, 1) element
5 from tbl
6 connect by level <= regexp_count(str, ',')+1;
ELEMENT
--------
1
2
4
5
SQL>
See this post for a function that returns a list element: REGEX to select nth value from a list, allowing for nulls
I have found my own way to split the given String using a FUNCTION
A TYPE should be declared as belows:
TYPE tabsplit IS TABLE OF VARCHAR2 (50)
INDEX BY BINARY_INTEGER;
And the FUNCTION should be written like this:
FUNCTION fn_split (mp_string IN VARCHAR2, mp_delimiter IN VARCHAR2)
RETURN tabsplit
IS
ml_point NUMBER (5, 0) := 1;
ml_sub_str VARCHAR2 (50);
i NUMBER (5, 0) := 1;
taboutput tabsplit;
ml_count NUMBER (5, 0) := 0;
BEGIN
WHILE i <= LENGTH (mp_string)
LOOP
FOR j IN i .. LENGTH (mp_string)
LOOP
IF SUBSTR (mp_string, j, 1) = mp_delimiter
THEN
ml_sub_str := SUBSTR (mp_string, ml_point, j - ml_point);
ml_point := j + 1;
i := ml_point;
i := i - 1;
taboutput (ml_count) := ml_sub_str;
ml_count := ml_count + 1;
EXIT;
END IF;
END LOOP;
i := i + 1;
END LOOP;
ml_sub_str := SUBSTR (mp_string, ml_point, LENGTH (mp_string));
taboutput (ml_count) := ml_sub_str;
RETURN taboutput;
END fn_split;
This FUNCTION can be used as belows:
DECLARE
taboutput tabsplit;
BEGIN
taboutput := fn_split ('1,2,3,4,5', ',');
FOR i IN 0 .. taboutput.COUNT - 1
LOOP
DBMS_OUTPUT.put_line (taboutput (i));
END LOOP;
END;
SELECT LEVEL AS id, REGEXP_SUBSTR('A,B,C,D', '[^,]+', 1, LEVEL) AS data
FROM dual
CONNECT BY REGEXP_SUBSTR('A,B,C,D', '[^,]+', 1, LEVEL) IS NOT NULL;

Is there a function to split a string in Oracle PL/SQL?

I need to write a procedure to normalize a record that have multiple tokens concatenated by one char. I need to obtain these tokens splitting the string and insert each one as a new record in a table. Does Oracle have something like a "split" function?
There is apex_util.string_to_table - see my answer to this question.
Also, prior to the existence of the above function, I once posted a solution here on my blog.
Update
In later versions of APEX, apex_util.string_to_table is deprecated, and a similar function apex_string.split is preferred.
If APEX_UTIL is not available, you have a solution using REGEXP_SUBSTR().
Inspired from http://nuijten.blogspot.fr/2009/07/splitting-comma-delimited-string-regexp.html :
DECLARE
I INTEGER;
TYPE T_ARRAY_OF_VARCHAR IS TABLE OF VARCHAR2(2000) INDEX BY BINARY_INTEGER;
MY_ARRAY T_ARRAY_OF_VARCHAR;
MY_STRING VARCHAR2(2000) := '123,456,abc,def';
BEGIN
FOR CURRENT_ROW IN (
with test as
(select MY_STRING from dual)
select regexp_substr(MY_STRING, '[^,]+', 1, rownum) SPLIT
from test
connect by level <= length (regexp_replace(MY_STRING, '[^,]+')) + 1)
LOOP
DBMS_OUTPUT.PUT_LINE(CURRENT_ROW.SPLIT);
MY_ARRAY(MY_ARRAY.COUNT) := CURRENT_ROW.SPLIT;
END LOOP;
END;
/
You have to roll your own. E.g.,
/* from :http://www.builderau.com.au/architect/database/soa/Create-functions-to-join-and-split-strings-in-Oracle/0,339024547,339129882,00.htm
select split('foo,bar,zoo') from dual;
select * from table(split('foo,bar,zoo'));
pipelined function is SQL only (no PL/SQL !)
*/
create or replace type split_tbl as table of varchar2(32767);
/
show errors
create or replace function split
(
p_list varchar2,
p_del varchar2 := ','
) return split_tbl pipelined
is
l_idx pls_integer;
l_list varchar2(32767) := p_list;
l_value varchar2(32767);
begin
loop
l_idx := instr(l_list,p_del);
if l_idx > 0 then
pipe row(substr(l_list,1,l_idx-1));
l_list := substr(l_list,l_idx+length(p_del));
else
pipe row(l_list);
exit;
end if;
end loop;
return;
end split;
/
show errors;
/* An own implementation. */
create or replace function split2(
list in varchar2,
delimiter in varchar2 default ','
) return split_tbl as
splitted split_tbl := split_tbl();
i pls_integer := 0;
list_ varchar2(32767) := list;
begin
loop
i := instr(list_, delimiter);
if i > 0 then
splitted.extend(1);
splitted(splitted.last) := substr(list_, 1, i - 1);
list_ := substr(list_, i + length(delimiter));
else
splitted.extend(1);
splitted(splitted.last) := list_;
return splitted;
end if;
end loop;
end;
/
show errors
declare
got split_tbl;
procedure print(tbl in split_tbl) as
begin
for i in tbl.first .. tbl.last loop
dbms_output.put_line(i || ' = ' || tbl(i));
end loop;
end;
begin
got := split2('foo,bar,zoo');
print(got);
print(split2('1 2 3 4 5', ' '));
end;
/
You can use regexp_substr(). Example:
create or replace type splitTable_Type is table of varchar2(100);
declare
l_split_table splitTable_Type;
begin
select
regexp_substr('SMITH,ALLEN,WARD,JONES','[^,]+', 1, level)
bulk collect into
l_split_table
from dual
connect by
regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) is not null;
end;
The query iterates through the comma separated string, searches for the comma (,) and then splits the string by treating the comma as delimiter. It returns the string as a row, whenever it hits a delimiter.
level in statement regexp_substr('SMITH,ALLEN,WARD,JONES','[^,]+', 1, level) refers to a pseudocolumn in Oracle which is used in a hierarchical query to identify the hierarchy level in numeric format: level in connect by
This only works in Oracle 10G and greater.
Basically, you use regex_substr to do a split on the string.
https://blogs.oracle.com/aramamoo/entry/how_to_split_comma_separated_string_and_pass_to_in_clause_of_select_statement
Edit:
Archived link: http://web.archive.org/web/20170304121704/https://blogs.oracle.com/aramamoo/entry/how_to_split_comma_separated_string_and_pass_to_in_clause_of_select_statement
The code:
select * from emp where ename in (
select regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) from dual
connect by regexp_substr('SMITH,ALLEN,WARD,JONES', '[^,]+', 1, level) is not null
);
You could use a combination of SUBSTR and INSTR as follows :
Example string : field = 'DE124028##$1048708##$000##$536967136##$'
The seperator being ##$.
To get the '1048708' for example :
If the field is of fixed length ( 7 here ) :
substr(field,instr(field,'##$',1,1)+3,7)
If the field is of variable length :
substr(field,instr(field,'##$',1,1)+3,instr(field,'##$',1,2) - (instr(field,'##$',1,1)+3))
You should probably look into SUBSTR and INSTR functions for more flexibility.
Please find next an example you may find useful
--1st substring
select substr('alfa#bravo#charlie#delta', 1,
instr('alfa#bravo#charlie#delta', '#', 1, 1)-1) from dual;
--2nd substring
select substr('alfa#bravo#charlie#delta', instr('alfa#bravo#charlie#delta', '#', 1, 1)+1,
instr('alfa#bravo#charlie#delta', '#', 1, 2) - instr('alfa#bravo#charlie#delta', '#', 1, 1) -1) from dual;
--3rd substring
select substr('alfa#bravo#charlie#delta', instr('alfa#bravo#charlie#delta', '#', 1, 2)+1,
instr('alfa#bravo#charlie#delta', '#', 1, 3) - instr('alfa#bravo#charlie#delta', '#', 1, 2) -1) from dual;
--4th substring
select substr('alfa#bravo#charlie#delta', instr('alfa#bravo#charlie#delta', '#', 1, 3)+1) from dual;
Best regards
Emanuele
function numinstr(p_source in varchar2,p_token in varchar2)
return pls_integer
is
v_occurrence pls_integer := 1;
v_start pls_integer := 1;
v_loc pls_integer;
begin
v_loc:=instr(p_source, p_token, 1, 1);
while v_loc > 0 loop
v_occurrence := v_occurrence+1;
v_start:=v_loc+1;
v_loc:=instr(p_source, p_token, v_start, 1);
end loop;
return v_occurrence-1;
end numinstr;
--
--
--
--
function get_split_field(p_source in varchar2,p_delim in varchar2,nth pls_integer)
return varchar2
is
v_num_delims pls_integer;
first_pos pls_integer;
final_pos pls_integer;
len_delim pls_integer := length(p_delim);
ret_len pls_integer;
begin
v_num_delims := numinstr(p_source,p_delim);
if nth < 1 or nth > v_num_delims+1 then
return null;
else
if nth = 1 then
first_pos := 1;
else
first_pos := instr(p_source, p_delim, 1, nth-1) + len_delim;
end if;
if nth > v_num_delims then
final_pos := length(p_source);
else
final_pos := instr(p_source, p_delim, 1, nth) - 1;
end if;
ret_len := (final_pos - first_pos) + 1;
return substr(p_source, first_pos, ret_len);
end if;
end get_split_field;
I needed a function that splits a clob and makes sure the function is usable in sql.
create or replace type vchar_tab is table of varchar2(4000)
/
create or replace function split(
p_list in clob,
p_separator in varchar2 default '|'
) return vchar_tab pipelined is
C_SQL_VCHAR_MAX constant integer:=4000;
C_MAX_AMOUNT constant integer:=28000;
C_SEPARATOR_LEN constant integer:=length(p_separator);
l_amount integer:=C_MAX_AMOUNT;
l_offset integer:=1;
l_buffer varchar2(C_MAX_AMOUNT);
l_list varchar2(32767);
l_index integer;
begin
if p_list is not null then
loop
l_index:=instr(l_list, p_separator);
if l_index > C_SQL_VCHAR_MAX+1 then
raise_application_error(-20000, 'item is too large for sql varchar2: len='||(l_index-1));
elsif l_index > 0 then -- found an item, pipe it
pipe row (substr(l_list, 1, l_index-1));
l_list:=substr(l_list, l_index+C_SEPARATOR_LEN);
elsif length(l_list) > C_SQL_VCHAR_MAX then
raise_application_error(-20001, 'item is too large for sql varchar2: length exceeds '||length(l_list));
elsif l_amount = C_MAX_AMOUNT then -- more to read from the clob
dbms_lob.read(p_list, l_amount, l_offset, l_buffer);
l_list:=l_list||l_buffer;
else -- read through the whole clob
if length(l_list) > 0 then
pipe row (l_list);
end if;
exit;
end if;
end loop;
end if;
return;
exception
when no_data_needed then -- this happens when you don't fetch all records
null;
end;
/
Test:
select *
from table(split('ASDF|IUYT|KJHG|ASYD'));
In Oracle, below SQL will split myString to substring:
WITH rws AS (
SELECT
'str1,STR2,stR3,StR4' myString
FROM
dual
) SELECT
regexp_substr(
myString,
'[^,]+',
1,
level
) value
FROM
rws
CONNECT BY
level <= length(myString) - length(
replace(
myString,
','
)
) + 1;
Result is:
str1
STR2
stR3
StR4
I like the look of that apex utility. However its also good to know about the standard oracle functions you can use for this: subStr and inStr
http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/functions001.htm
There is a simple way folks. Use REPLACE function. Here is an example of comma separated string ready to be passed to IN clause.
In PL/SQL:
StatusString := REPLACE('Active,Completed', ',', ''',''');
In SQL Plus:
Select REPLACE('Active,Completed', ',', ''',''') from dual;

Resources