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;
Related
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
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>
This is something that should be easey but I just can´t get it work.
I come from java so maby I have a error in my thinking here.
What I want to do is that I have a string with two letters like 't4' or 'pq'.
Now I just want to get each of the chracters in the string as an own string.
So I do:
firstString := myString[0];
but I don´t even get this compiled.
So I figured that they start counting form 1 and put 1 as an index.
Now I do this in a while loop and the first time I go through it it works fine. Then the second time the results are just empty or wrong numbers.
What am I missing here?
(I also tried copy but that doesn´t work either!)
while i < 10 do
begin
te := 'te';
a := te[1];
b := te[2];
i := i +1;
end;
the first loop a is 't' and b is 'e' as I would expect. The second time a is '' and b ist 't' which I don´t understand!
Strings are 1-based, not zero-based. Try the following, after adding StrUtils to your Uses list (for DupeString):
var
MyString : String;
begin
MyString := '12345';
Caption := StringOfChar(MyString[1], 8) + ':' + DupeString(Copy(MyString, 3, 2), 4);
You could split it up to mke it easier to follow, of course:
var
MyString,
S1,
S2,
S3: String;
begin
MyString := '12345';
S1 := StringOfChar(MyString[1], 8);
S2 := Copy(MyString, 3, 2);
S3 := DupeString(S2, 4);
Caption := S1 + ':' + S3;
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;
I am trying to convert string to long in oracle 8. How do I do that? I have only version from blob to char.
function lob2char(clob_col clob) return varchar2 IS
buffer varchar2(4000);
amt BINARY_INTEGER := 4000;
pos INTEGER := 1;
l clob;
bfils bfile;
l_var varchar2(4000):='';
begin
LOOP
if dbms_lob.getlength(clob_col)<=4000 THEN
dbms_lob.read (clob_col, amt, pos, buffer);
l_var := l_var||buffer;
pos:=pos+amt;
ELSE
l_var:= 'Cannot convert. Exceeded varchar2 limit';
exit;
END IF;
END LOOP;
return l_var;
EXCEPTION
WHEN NO_DATA_FOUND THEN
return l_var;
END;
Cannot find anywhere on google.
I'm not sure if you're just over-thinking this. A varchar2 is always going to be smaller than a long or clob could be, so you can just assign your value. It's going the other way that causes problems. You can just do this in SQL:
insert into t42 (long_val, clob_val)
values ('any string up to 4000 chars', 'any other string up to 4000 chars');
Or from PL/SQL:
declare
char_val varchar2(4000);
begin
char_val := 'another string';
insert into t42 (long_val, clob_val)
values (char_val, char_val);
end;
/
SQl Fiddle demo. I believe that all worked the same in 8i as it does now in 11g.
The PL/SQL varchar2 can be up to 32k and still be inserted like this.