I can't find a solution about how to split a comma-delimited string in ORACLE. Searched a lot, nothing works for my case
Code
DECLARE
TYPE T_ARRAY_OF_VARCHAR IS TABLE OF VARCHAR2(2000) INDEX BY BINARY_INTEGER;
MY_ARRAY T_ARRAY_OF_VARCHAR;
MY_STRING VARCHAR2(2000) := '12 3,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 || '<');
--DBMS_OUTPUT.PUT_LINE(CURRENT_ROW.SPLIT);
MY_ARRAY(MY_ARRAY.COUNT) := CURRENT_ROW.SPLIT;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Array Size:' || MY_ARRAY.COUNT);
END;
/
The output is:
>12 3<
>456<
>abc<
>def<
><
Array Size:5
The empty value is out of order!!!!
Try this for the parsing the list part. It handles NULLS:
SQL> select regexp_substr('12 3,456,,abc,def', '(.*?)(,|$)', 1, level, null, 1) SPLIT, level
from dual
connect by level <= regexp_count('12 3,456,,abc,def',',') + 1
ORDER BY level;
SPLIT LEVEL
----------------- ----------
12 3 1
456 2
3
abc 4
def 5
SQL>
Unfortunately when you search for regex's for parsing lists, you will always find this form which does NOT handle nulls and should be avoided: '[^,]+'. See here for more info: Split comma separated values to columns in Oracle.
Try xmltable and flwor expresion.
The following example is not secure and throw error if you put string without comma. But is simpler to understand.
select xmlcast(column_value as varchar2(2000)) value_list
from xmltable('for $val in ora:tokenize($strList,",")
return $val'
passing '12 3,456,,abc,def' as "strList"
);
And secured version.
select xmlcast(column_value as varchar2(2000)) value_list
from xmltable('for $val at $index in ora:tokenize(concat(",",$strList),",")
where $index > 1
return $val' passing '12 3,456,,abc,def' as "strList"
);
Little modification to your query, assuming you can pick one char which will not be present in MY_STRING, e.g. pipe |
with test as
(select '12 3,456,,,,abc,def' MY_STRING from dual)
select trim('|' from regexp_substr(regexp_replace(MY_STRING,',,',',|,|'),'[^,]+',1,level)) SPLIT
from test
connect by level <= length (regexp_replace(MY_STRING, '[^,]+')) + 1;
Output:
SPLIT
-----------------------
12 3
456
(null)
(null)
(null)
abc
def
No need of PL/SQL, you could do it in plain SQL. See Split comma delimited strings in a table in Oracle.
Using MODEL clause:
WITH sample_data AS (
SELECT '12 3,456,,,,,abc,def' str FROM dual
)
-- end of sample_data mimicking real table
,
model_param AS (
SELECT str AS orig_str ,
','
|| str
|| ',' AS mod_str ,
1 AS start_pos ,
Length(str) AS end_pos ,
(LENGTH(str) -
LENGTH(REPLACE(str, ','))) + 1 AS element_count ,
0 AS element_no ,
ROWNUM AS rn
FROM sample_data )
SELECT trim(Substr(mod_str, start_pos, end_pos-start_pos)) str
FROM (
SELECT *
FROM model_param
MODEL PARTITION BY ( rn, orig_str, mod_str)
DIMENSION BY (element_no)
MEASURES (start_pos, end_pos, element_count)
RULES ITERATE (2000)
UNTIL (ITERATION_NUMBER+1 = element_count[0])
( start_pos[ITERATION_NUMBER+1] =
instr(cv(mod_str), ',', 1, cv(element_no)) + 1,
end_pos[ITERATION_NUMBER+1] =
instr(cv(mod_str), ',', 1, cv(element_no) + 1) )
)
WHERE element_no != 0
ORDER BY mod_str ,
element_no
/
Output
STR
----------------------
12 3
456
abc
def
8 rows selected.
If you want to do it in PL/SQL, then you could use a pipelined table function:
SQL> CREATE OR REPLACE TYPE test_type
2 AS
3 TABLE OF VARCHAR2(100)
4 /
Type created.
SQL> CREATE OR REPLACE FUNCTION comma_to_table(
2 p_list IN VARCHAR2)
3 RETURN test_type PIPELINED
4 AS
5 l_string LONG := p_list || ',';
6 l_comma_index PLS_INTEGER;
7 l_index PLS_INTEGER := 1;
8 BEGIN
9 LOOP
10 l_comma_index := INSTR(l_string, ',', l_index);
11 EXIT
12 WHEN l_comma_index = 0;
13 PIPE ROW ( TRIM(SUBSTR(l_string, l_index, l_comma_index - l_index)));
14 l_index := l_comma_index + 1;
15 END LOOP;
16 RETURN;
17 END comma_to_table;
18 /
Function created.
Let's see the output:
SQL> SELECT *
2 FROM TABLE(comma_to_table('12 3,456,,,,,abc,def'))
3 /
COLUMN_VALUE
------------------------------------------------------------------------------
12 3
456
abc
def
8 rows selected.
SQL>
Related
I have read only access to this particular database, hence I am not allowed to create any functions.
I am trying to achieve the below one using select statement.
How to get the value for column reading as '=4*10*2' as 80 in sql - implement the same using select query without creating the function.
I used the below query:
qty
----
10*4*2
4*3*1
5*1*1
select case when length=1 then substr(qty,1,1)
when length=2 then substr(qty,1,1)*substr(qty,2,1)
when length=3 then substr(qty,1,1)*substr(qty,2,1)*substr(qty,3,1)
else qty
end
from (select replace(qty,'*','') as qty from table_quants);
The above query works fine until and unless the value does not contain 10s or zeroes.
i.e,
qty
10*4*2 0 ------> which is not correct, I should get 80 instead of zero
4*3*1 12
5*1*1 5
Can someone pls help me out.
If it were Oracle, then
SQL> with table_quants (id, qty) as
2 -- sample data
3 (select 1, '10*4*2' from dual union all
4 select 2, '4*3*1' from dual union all
5 select 3, '5*1*1' from dual
6 ),
7 split_qty as
8 -- split QTY column to rows
9 (select id,
10 qty,
11 regexp_substr(qty, '[^*]+', 1, column_value) val
12 from table_quants cross join
13 table(cast(multiset(select level from dual
14 connect by level <= regexp_count(qty, '\*') + 1
15 ) as sys.odcinumberlist))
16 )
17 -- compute the result
18 select id,
19 qty,
20 round(exp(sum(ln(val)))) result
21 from split_qty
22 group by id, qty
23 order by id;
ID QTY RESULT
---------- ------ ----------
1 10*4*2 80
2 4*3*1 12
3 5*1*1 5
SQL>
XMLTABLE is often a shortcut for simple expressions, eg
SQL> create table t ( expr varchar2(20));
Table created.
SQL> insert into t values ('1+2');
1 row created.
SQL> insert into t values ('1+2*7-3+11');
1 row created.
SQL> select * from t, xmltable(t.expr);
EXPR COLUMN_VALUE
-------------------- ------------------------------
1+2 3
1+2*7-3+11 23
Same idea with SQL Server, just a bit shorter:
with table_quants (id, qty) as
-- sample data
(select 1, '10*4*2' union all
select 2, '4*3*1' union all
select 3, '5*1*1'
)
select id, exp( (select sum(log(value)) from string_split(qty,'*')) ) result
from table_quants
outputs
id result
----------- ----------------------
1 80
2 12
3 5
(3 rows affected)
In Oracle, you can use a recursive sub-query factoring clause and simple string functions (which, in this testing, was faster than CROSS JOINing with a correlated TABLE collection expression generated by CAST and MULTISET):
WITH multiplied_values ( qty, value, start_pos, end_pos ) AS (
SELECT qty,
1,
1,
INSTR( qty, '*', 1 )
FROM table_name
UNION ALL
SELECT qty,
value * SUBSTR( qty, start_pos, end_pos - start_pos ),
end_pos + 1,
INSTR( qty, '*', end_pos + 1 )
FROM multiplied_values
WHERE end_pos > 0
)
SELECT qty,
value * SUBSTR( qty, start_pos ) AS value
FROM multiplied_values
WHERE end_pos = 0;
Which, for your sample data:
CREATE TABLE table_name ( qty ) AS
SELECT '10*4*2' FROM DUAL UNION ALL
SELECT '4*3*1' FROM DUAL UNION ALL
SELECT '5*1*1' FROM DUAL;
Outputs:
QTY | VALUE
:----- | ----:
10*4*2 | 80
4*3*1 | 12
5*1*1 | 5
db<>fiddle here
The equivalent in SQL Server is:
WITH multiplied_values ( qty, value, start_pos, end_pos ) AS (
SELECT qty,
1,
1,
CHARINDEX( '*', qty, 1 )
FROM table_name
UNION ALL
SELECT qty,
value * CAST( SUBSTRING( qty, start_pos, end_pos - start_pos ) AS INT ),
end_pos + 1,
CHARINDEX( '*', qty, end_pos + 1 )
FROM multiplied_values
WHERE end_pos > 0
)
SELECT qty,
value * CAST( SUBSTRING( qty, start_pos, LEN( qty ) - start_pos + 1 ) AS INT )
AS value
FROM multiplied_values
WHERE end_pos = 0;
db<>fiddle here
In Oracle you can do this:
with function evaluate_expression(p_expression in varchar2)
return number
is
l_cursor integer default dbms_sql.open_cursor;
l_feedback integer default 0;
l_retval number; /* with divisions we might get a NUMBER */
begin
dbms_sql.parse(l_cursor,'begin :ret_val := ' || p_expression ||'; end;', dbms_sql.native );
dbms_sql.bind_variable(l_cursor,':ret_val',l_retval);
l_feedback := dbms_sql.execute(l_cursor);
dbms_sql.variable_value(l_cursor, ':ret_val', l_retval);
dbms_sql.close_cursor(l_cursor);
return l_retval;
exception
when others then
dbms_sql.close_cursor(l_cursor);
if (sqlcode=-1476) then
return 0;
else
raise;
end if;
end;
select evaluate_expression('(3*(2+3)+10-1)/2') from dual;
EVALUATE_EXPRESSION('(3*(2+3)+10-1)/2')
---------------------------------------
12
Or if you have many expressions to evaluate you can create a view:
create view exprs as
select '(3*(2+3)+10-1)/2' expr from dual union all
select '1+2+3+4+5+6' from dual union all
select '1*2*3*4*5*6' from dual
;
and use the above to resolve the expressions:
with function evaluate_expression(p_expression in varchar2)
return number
is
l_cursor integer default dbms_sql.open_cursor;
l_feedback integer default 0;
l_retval number; /* with divisions we might get a NUMBER */
begin
dbms_sql.parse(l_cursor,'begin :ret_val := ' || p_expression ||'; end;', dbms_sql.native );
dbms_sql.bind_variable(l_cursor,':ret_val',l_retval);
l_feedback := dbms_sql.execute(l_cursor);
dbms_sql.variable_value(l_cursor, ':ret_val', l_retval);
dbms_sql.close_cursor(l_cursor);
return l_retval;
exception
when others then
dbms_sql.close_cursor(l_cursor);
if (sqlcode=-1476) then
return 0;
else
raise;
end if;
end;
select expr||'='||evaluate_expression(expr) expr
from (select expr from exprs)
;
EXPR
---------------------------------------------------------
(3*(2+3)+10-1)/2=12
1+2+3+4+5+6=21
1*2*3*4*5*6=720
I want to print in Oracle.
Input string : 'Tprintthisstring'
Output string: 'T,pri,ntt,his,str,ing'
Use a regular expression to prepend a comma before every block of 3 lower-case letters.
Query:
SELECT REGEXP_REPLACE( 'Tprintthisstring', '([a-z]{3})', ',\1' )
FROM DUAL;
Output:
| REGEXP_REPLACE('TPRINTTHISSTRING','([A-Z]{3})',',\1') |
| :---------------------------------------------------- |
| T,pri,ntt,his,str,ing |
db<>fiddle here
Well, this returns the result you want, but I have no idea whether it'll work always as you didn't explain rules that lead from source to target.
SQL> with test (col) as
2 (select 'Tprintthisstring' from dual
3 ),
4 temp as
5 -- c1 is the first letter
6 -- then split the rest into groups of 3 letters (rows)
7 (select substr(substr(col, 2), 3 * (level - 1) + 1, 3) c2,
8 level lvl,
9 substr(col, 1, 1) c1
10 from test
11 connect by level <= length(substr(col, 2))
12 )
13 -- aggregate the c2 string back, separated by comma
14 select c1 ||','||
15 listagg(c2, ',') within group (order by lvl) result
16 from temp
17 where c2 is not null
18 group by c1;
RESULT
-------------------------------------------------------------------------------
T,pri,ntt,his,str,ing
SQL>
I'm not sure why you tagged it as PL/SQL and what kind of PL/SQL should it be; an anonymous block? Stored procedure? Whatever it is, the above query can easily be converted to PL/SQL.
set serveroutput ON;
DECLARE
l VARCHAR2 (256);
l1 VARCHAR2 (256);
len NUMBER;
str1 VARCHAR (20);
str2 VARCHAR (20);
a NUMBER (10);
counter NUMBER (10);
i NUMBER (10);
p_string VARCHAR2(1000) := 'aaasasdasd,rrt';
decml NUMBER (10) := 3;
BEGIN
a := 1;
i := 1;
l := Substr (p_string, Instr (p_string, ',') + 1);
l1 := Substr (p_string, 0, Instr (p_string, ',') - 1);
len := Length (l1);
IF len <= decml THEN
str1 := l1
||','
||l;
ELSE
counter := Floor (len / decml);
FOR a IN REVERSE i .. counter LOOP
str1 := str1
|| '.'
|| Substr (l1, -decml * a, decml);
END LOOP;
IF ( counter * decml = len ) THEN
str1 := Substr (str1, 2, Length (str1))
|| ','
|| l;
ELSE
str2 := Substr (l1, 1, ( len - ( counter * decml ) ));
str1 := str2
|| str1
|| ','
|| l;
END IF;
END IF;
dbms_output.Put_line(str1);
END;
The table temp has the columns "word" and "sentence". Below code checks, if the sentence has any words from the word column. If the word exists, the word will be replaced with a URL (contains the word itself and its id). The code works fine for about 1-10 rows. The table has about 50k records. It consumes the whole of the temp space. How can I review and fine-tune the query?
Requirement: There are 50k words and sentence. The requirement is to replace the words in the sentences with a URL (contains the word and its id) if any of the words exist in the word column. While looking for the words, the search has to be case insensitive. Also, we need to retain the same case in the sentence while replacing with the URL.
Create table temp(
id NUMBER,
word VARCHAR2(1000),
Sentence VARCHAR2(2000)
);
insert into temp
SELECT 1,'automation testing', 'automtestingation TeStInG TEST is popular kind of testing' FROM DUAL UNION ALL
SELECT 2,'testing','manual testing' FROM DUAL UNION ALL
select 2,'test', 'test' FROM DUAL UNION ALL
SELECT 3,'manual testing','this is an old method of testing' FROM DUAL UNION ALL
SELECT 4,'punctuation','automation testing,manual testing,punctuation,automanual testing-testing' FROM DUAL UNION ALL
SELECT 5,'B-number analysis','B-number analysis table' FROM DUAL UNION ALL
SELECT 6,'B-number analysis table','testing B-number analysis' FROM DUAL UNION ALL
SELECT 7,'Not Matched','testing testing testing' FROM DUAL
SQL Types:
CREATE TYPE stringlist IS TABLE OF VARCHAR2(4000);
/
CREATE TYPE intlist IS TABLE OF NUMBER(20,0);
/
PLSQL Function
CREATE FUNCTION replace_words(
word_list IN stringlist,
id_list IN intlist,
sentence IN temp.sentence%TYPE
) RETURN temp.sentence%TYPE
IS
p_sentence temp.sentence%TYPE := UPPER( sentence );
p_pos PLS_INTEGER := 1;
p_min_word_index PLS_INTEGER;
p_word_index PLS_INTEGER;
p_start PLS_INTEGER;
p_index PLS_INTEGER;
o_sentence temp.sentence%TYPE;
BEGIN
LOOP
p_min_word_index := NULL;
p_index := NULL;
FOR i IN 1 .. word_list.COUNT LOOP
p_word_index := p_pos;
LOOP
p_word_index := INSTR( p_sentence, word_list(i), p_word_index );
EXIT WHEN p_word_index = 0;
IF ( p_word_index > 1
AND REGEXP_LIKE( SUBSTR( p_sentence, p_word_index - 1, 1 ), '\w' )
)
OR REGEXP_LIKE( SUBSTR( p_sentence, p_word_index + LENGTH( word_list(i) ), 1 ), '\w' )
THEN
p_word_index := p_word_index + 1;
CONTINUE;
END IF;
IF p_min_word_index IS NULL OR p_word_index < p_min_word_index THEN
p_min_word_index := p_word_index;
p_index := i;
END IF;
EXIT;
END LOOP;
END LOOP;
IF p_index IS NULL THEN
o_sentence := o_sentence || SUBSTR( sentence, p_pos );
EXIT;
ELSE
o_sentence := o_sentence
|| SUBSTR( sentence, p_pos, p_min_word_index - p_pos )
|| 'http://localhost/'
|| id_list(p_index)
|| '/<u>'
|| SUBSTR( sentence, p_min_word_index, LENGTH( word_list( p_index ) ) )
|| '</u>';
p_pos := p_min_word_index + LENGTH( word_list( p_index ) );
END IF;
END LOOP;
RETURN o_sentence;
END;
/
MERGE
MERGE INTO temp dst
USING (
WITH lists ( word_list, id_list ) AS (
SELECT CAST(
COLLECT(
UPPER( word )
ORDER BY LENGTH( word ) DESC, UPPER( word ) ASC, ROWNUM
)
AS stringlist
),
CAST(
COLLECT(
id
ORDER BY LENGTH( word ) DESC, UPPER( word ) ASC, ROWNUM
)
AS intlist
)
FROM temp
)
SELECT t.ROWID rid,
replace_words(
word_list,
id_list,
sentence
) AS replaced_sentence
FROM temp t
CROSS JOIN lists
) src
ON ( dst.ROWID = src.RID )
WHEN MATCHED THEN
UPDATE SET sentence = src.replaced_sentence;
I separate words (with ids) from sentences, and I put words in lowercase because you want a case insensitive search anyway. If I find two matches at the same position in the sentence, I choose the longer one. If there are overlaps ('manual testing' and 'testing strategy'), I always choose the "word" that comes first in the sentence.
Best regards,
Stew Ashton
SQL> Create table temp(
2 id NUMBER,
3 word VARCHAR2(1000),
4 Sentence VARCHAR2(2000)
5 );
SQL> insert into temp
2 SELECT 1,'automation testing', 'automtestingation TeStInG TEST is popular kind of testing' FROM DUAL UNION ALL
3 SELECT 2,'testing','manual testing' FROM DUAL UNION ALL
4 select 2,'test', 'test' FROM DUAL UNION ALL
5 SELECT 3,'manual testing','this is an old method of testing' FROM DUAL UNION ALL
6 SELECT 4,'punctuation','automation Testing,manual tEsting,punctuation,automanual teSting-tesTing' FROM DUAL UNION ALL
7 SELECT 5,'B-number analysis','B-number analysis table' FROM DUAL UNION ALL
8 SELECT 6,'B-number analysis table','testing B-number analysis' FROM DUAL UNION ALL
9 SELECT 7,'Not Matched','Testing tEsting teSting' FROM DUAL;
SQL> create table sentences as select sentence from temp;
SQL> create table words cache as
2 select length(word) word_length,
3 min(id) id,
4 lower(word) word
5 from temp
6 group by length(word), lower(word);
SQL> insert into sentences
2 select listagg(word, ',') within group(order by word)
3 from words;
SQL> insert into sentences values('Nothing matches here');
SQL> commit;
SQL> declare
2 cursor cur_sentences is
3 select rowid rid, sentence from sentences s
4 where exists (
5 select null from words
6 where instr(lower(s.sentence), word) > 0
7 )
8 for update;
9 type tt_sentences is table of cur_sentences%rowtype;
10 lt_sentences tt_sentences;
11 lt_sentences_new tt_sentences;
12
13 function change_sentence(p_sentence in sentences.sentence%type)
14 return sentences.sentence%type is
15 cursor cur_words(cp_sentence in sentences.sentence%type) is
16 with recurse (pos, word_length, id, word) as (
17 select regexp_instr(cp_sentence, '(^|\W)('||word||')(\W|$)', 1, 1, 0, 'i', 2),
18 word_length, id, word
19 from words
20 where regexp_instr(cp_sentence, '(^|\W)('||word||')(\W|$)', 1, 1, 0, 'i', 2) > 0
21 union all
22 select regexp_instr(cp_sentence, '(^|\W)('||word||')(\W|$)', pos+1, 1, 0, 'i', 2),
23 word_length, id, word
24 from recurse
25 where regexp_instr(cp_sentence, '(^|\W)('||word||')(\W|$)', pos+1, 1, 0, 'i', 2) > 0
26 )
27 select pos, word_length, id, word,
28 substr(cp_sentence, pos, length(word)) new_word
29 from recurse
30 order by pos, word_length desc;
31 type tt_words is table of cur_words%rowtype;
32 lt_words tt_words;
33 lt_words_kept tt_words:= new tt_words();
34 l_pos number := 0;
35 l_sentence sentences.sentence%type := p_sentence;
36 begin
37 open cur_words(p_sentence);
38 fetch cur_words bulk collect into lt_words;
39 for i in 1..lt_words.count loop
40 if l_pos < lt_words(i).pos then
41 l_pos := lt_words(i).pos + lt_words(i).word_length;
42 lt_words_kept.extend;
43 lt_words_kept(lt_words_kept.count) := lt_words(i);
44 end if;
45 end loop;
46 close cur_words;
47 for i in reverse 1..lt_words_kept.count loop
48 l_sentence := regexp_replace(
49 l_sentence,
50 lt_words_kept(i).new_word,
51 'http://localhost/'||lt_words_kept(i).id||'/<u>'||lt_words_kept(i).new_word||'</u>',
52 lt_words_kept(i).pos,
53 1
54 );
55 end loop;
56 return l_sentence;
57 exception when others then
58 close cur_words;
59 raise;
60 end change_sentence;
61
62 begin
63 open cur_sentences;
64 loop
65 fetch cur_sentences bulk collect into lt_sentences limit 100;
66 exit when lt_sentences.count = 0;
67 lt_sentences_new := new tt_sentences();
68 lt_sentences_new.extend(lt_sentences.count);
69 for i in 1..lt_sentences.count loop
70 lt_sentences_new(i).sentence := change_sentence(lt_sentences(i).sentence);
71 end loop;
72 forall i in 1..lt_sentences.count
73 update sentences set sentence = lt_sentences_new(i).sentence where rowid = lt_sentences(i).rid;
74 exit when cur_sentences%notfound;
75 end loop;
76 close cur_sentences;
77 exception when others then
78 if cur_sentences%isopen then
79 close cur_sentences;
80 raise;
81 end if;
82 end;
83 /
PL/SQL procedure successfully completed.
SQL> select * from sentences order by 1;
SENTENCE
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nothing matches here
automtestingation http://localhost/2/<u>TeStInG</u> http://localhost/2/<u>TEST</u> is popular kind of http://localhost/2/<u>testing</u>
http://localhost/1/<u>automation Testing</u>,http://localhost/3/<u>manual tEsting</u>,http://localhost/4/<u>punctuation</u>,automanual http://localhost/2/<u>teSting</u>-http://localhost/2/<u>tesTing</u>
http://localhost/1/<u>automation testing</u>,http://localhost/5/<u>b-number analysis</u>,http://localhost/6/<u>b-number analysis table</u>,http://localhost/3/<u>manual testing</u>,http://localhost/7/<u>not matched</u>,http://localhost/4/<u>punctuation</u>,http://localhost/2/<u>test</u>,http://localhost/2/<u>testing</u>
http://localhost/2/<u>Testing</u> http://localhost/2/<u>tEsting</u> http://localhost/2/<u>teSting</u>
http://localhost/2/<u>test</u>
http://localhost/2/<u>testing</u> http://localhost/5/<u>B-number analysis</u>
http://localhost/3/<u>manual testing</u>
http://localhost/6/<u>B-number analysis table</u>
this is an old method of http://localhost/2/<u>testing</u>
I have column in my database where the values are coming like the following:
3862,3654,3828
In dummy column any no. of comma separated values can come. I tried with following query but it is creating duplicate results.
select regexp_substr(dummy,'[^,]+',1,Level) as dummycol
from (select * from dummy_table)
connect by level <= length(REGEXP_REPLACE(dummy,'[^,]+'))+1
I am not understanding the problem. Can anyone can help?
Works perfectly for me -
SQL> WITH dummy_table AS(
2 SELECT '3862,3654,3828' dummy FROM dual
3 )
4 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
5 FROM dummy_table
6 CONNECT BY level <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
7 /
DUMMYCOL
--------------
3862
3654
3828
SQL>
There are many other ways of achieving it. Read Split single comma delimited string into rows.
Update Regarding the duplicates when the column is used instead of a single string value. Saw the use of DBMS_RANDOM in the PRIOR clause to get rid of the cyclic loop here
Try the following,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
7 FROM dummy_table
8 CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
9 AND prior rn = rn
10 AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL
11 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
SQL>
Update 2
Another way,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678,xyz' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(t.dummy, '[^,]+', 1, levels.column_value)) AS dummycol
7 FROM dummy_table t,
8 TABLE(CAST(MULTISET
9 (SELECT LEVEL
10 FROM dual
11 CONNECT BY LEVEL <= LENGTH (regexp_replace(t.dummy, '[^,]+')) + 1
12 ) AS sys.OdciNumberList)) LEVELS
13 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
xyz
6 rows selected.
SQL>
Giving a PL/SQL example where parsing over a table with an ID and column name. This will parse and print out each ID and the parsed value which could then be inserted into a new table or used in some other way.
Input
Column_ID Column_Name
123 (3862,3654,3828)
Output
Column_ID Column_Name
123 3862
123 3654
123 3828
PL/SQL Code
declare
table_name1 varchar2(1000);
string_to_parse varchar2(2000); -- assign string to table name
string_length number := 0; -- string length for loop
string_value varchar2(2000); -- string value to store value in
column_id number;
begin
--some table in the format '123' as column_id, '(3862,3654,3828)' as column_name
--remove the parenthesis or other special characters if needed
update some_table t
set t.column_name = regexp_replace(t.column_name,'\(|\)','');
commit;
for i in (
select * from some_table
) loop
column_id := i.column_id; --assign the id of the colors
string_to_parse := i.column_name; -- assign string to be parsed
if string_to_parse is null then
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
else
--String to parse is the comma
string_to_parse := string_to_parse||',';
string_length := length(string_to_parse) - length(replace(string_to_parse,',',''));
-- Loop through string from parameter
for i in 1 .. string_length loop
-- [^,] matches any character except for the ,
select regexp_substr(string_to_parse,'[^,]+',1,i)
into string_value -- stores value into string_value
from dual; -- dual is a dummy table to work around
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
--clear out the string value
string_value := null;
end loop;
end if;
end loop;
end;
I have column in my database where the values are coming like the following:
3862,3654,3828
In dummy column any no. of comma separated values can come. I tried with following query but it is creating duplicate results.
select regexp_substr(dummy,'[^,]+',1,Level) as dummycol
from (select * from dummy_table)
connect by level <= length(REGEXP_REPLACE(dummy,'[^,]+'))+1
I am not understanding the problem. Can anyone can help?
Works perfectly for me -
SQL> WITH dummy_table AS(
2 SELECT '3862,3654,3828' dummy FROM dual
3 )
4 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
5 FROM dummy_table
6 CONNECT BY level <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
7 /
DUMMYCOL
--------------
3862
3654
3828
SQL>
There are many other ways of achieving it. Read Split single comma delimited string into rows.
Update Regarding the duplicates when the column is used instead of a single string value. Saw the use of DBMS_RANDOM in the PRIOR clause to get rid of the cyclic loop here
Try the following,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(dummy,'[^,]+',1,Level)) AS dummycol
7 FROM dummy_table
8 CONNECT BY LEVEL <= LENGTH(REGEXP_REPLACE(dummy,'[^,]+'))+1
9 AND prior rn = rn
10 AND PRIOR DBMS_RANDOM.VALUE IS NOT NULL
11 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
SQL>
Update 2
Another way,
SQL> WITH dummy_table AS
2 ( SELECT 1 rn, '3862,3654,3828' dummy FROM dual
3 UNION ALL
4 SELECT 2, '1234,5678,xyz' dummy FROM dual
5 )
6 SELECT trim(regexp_substr(t.dummy, '[^,]+', 1, levels.column_value)) AS dummycol
7 FROM dummy_table t,
8 TABLE(CAST(MULTISET
9 (SELECT LEVEL
10 FROM dual
11 CONNECT BY LEVEL <= LENGTH (regexp_replace(t.dummy, '[^,]+')) + 1
12 ) AS sys.OdciNumberList)) LEVELS
13 /
DUMMYCOL
--------------
3862
3654
3828
1234
5678
xyz
6 rows selected.
SQL>
Giving a PL/SQL example where parsing over a table with an ID and column name. This will parse and print out each ID and the parsed value which could then be inserted into a new table or used in some other way.
Input
Column_ID Column_Name
123 (3862,3654,3828)
Output
Column_ID Column_Name
123 3862
123 3654
123 3828
PL/SQL Code
declare
table_name1 varchar2(1000);
string_to_parse varchar2(2000); -- assign string to table name
string_length number := 0; -- string length for loop
string_value varchar2(2000); -- string value to store value in
column_id number;
begin
--some table in the format '123' as column_id, '(3862,3654,3828)' as column_name
--remove the parenthesis or other special characters if needed
update some_table t
set t.column_name = regexp_replace(t.column_name,'\(|\)','');
commit;
for i in (
select * from some_table
) loop
column_id := i.column_id; --assign the id of the colors
string_to_parse := i.column_name; -- assign string to be parsed
if string_to_parse is null then
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
else
--String to parse is the comma
string_to_parse := string_to_parse||',';
string_length := length(string_to_parse) - length(replace(string_to_parse,',',''));
-- Loop through string from parameter
for i in 1 .. string_length loop
-- [^,] matches any character except for the ,
select regexp_substr(string_to_parse,'[^,]+',1,i)
into string_value -- stores value into string_value
from dual; -- dual is a dummy table to work around
--at this point insert into a new table, or do whatever else you need
dbms_output.put_line(column_id || ' ' || string_value);
--clear out the string value
string_value := null;
end loop;
end if;
end loop;
end;