How to tune an Oracle SQL query - sql

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>

Related

How to rearrange the letter in string in alphabetical order in SQL

How to rearrange the letter in string in alphabetical order in SQL
For example
cbaz to abcz
You can split the string up into characters and then aggregate:
WITH characters ( rid, value, ch, i, l ) AS (
SELECT ROWID,
value,
SUBSTR(value, 1, 1),
1,
LENGTH(value)
FROM table_name
UNION ALL
SELECT rid,
value,
SUBSTR(value, i + 1, 1),
i + 1,
l
FROM characters
WHERE i < l
)
SELECT MAX( value ) AS original,
LISTAGG(ch) WITHIN GROUP ( ORDER BY ch ) AS ordered
FROM characters
GROUP BY rid
or:
SELECT value As original,
ordered
FROM table_name t
CROSS APPLY (
SELECT LISTAGG(SUBSTR(t.value, LEVEL, 1))
WITHIN GROUP (ORDER BY SUBSTR(t.value, LEVEL, 1)) AS ordered
FROM DUAL
CONNECT BY LEVEL <= LENGTH(t.value)
)
Which, for the sample data:
CREATE TABLE table_name ( value ) AS
SELECT 'cbaz' FROM DUAL UNION ALL
SELECT 'zyx' FROM DUAL UNION ALL
SELECT 'zyx' FROM DUAL;
Outputs:
ORIGINAL
ORDERED
cbaz
abcz
zyx
xyz
zyx
xyz
db<>fiddle here
Just for fun, you could do this programmatically:
with function sort_letters
( p_str varchar2 )
return varchar2
as
type charList is table of simple_integer index by varchar2(1);
letters charList;
letter varchar2(1);
sorted_letters long;
begin
if p_str is not null then
for i in 1..length(p_str) loop
letter := substr(p_str,i,1);
letters(letter) :=
case
when letters.exists(letter) then letters(letter) +1
else 1
end;
end loop;
letter := letters.first;
loop
sorted_letters := sorted_letters || rpad(letter, letters(letter), letter);
letter := letters.next(letter);
exit when letter is null;
end loop;
end if;
return sorted_letters;
end;
select sort_letters('abracadabra')
from dual
/
SORT_LETTERS('ABRACADABRA')
---------------------------
aaaaabbcdrr

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

Work Around for PL/SQL to do column validation

I need to create a procedure to validate number of digits between 2 columns. I have some reason that this handling won't do in Java so it needs to be a stored procedure.
It first will get the template(result_format) from one of my table and itself contain data like
5,5,5,5,5,5,5,5,5,5,4,4,4,4,4,4,4,4,4,4,3,3,3,2,2,2,2
then the argument P_RESULT will have input like
16768,74300,56212,38614,12250,52274,73018,32467,12618,48801,4257,6831,5436,4757,9395,5294,3687,3408,2803,1680,848,695,479,81,58,28,27
then I need to compare and count the first data from first result [16768] to the result_format [5] to see whether it contains 5 digits as per the result_format, then continue till end of the result.
if it detect different in the length of the result to the result format it will throw exception.
My procedure is below, it has compilation errors, it's because of it can't search my temporary table when i trying to put them into a temporary table and start my validation. [line 28]
create or replace procedure RESULT_VALIDATION(P_LOTTERY VARCHAR2,
P_RESULT VARCHAR2 ) as
V_TEMPLATE VARCHAR2(10 BYTE);
V_RESULT RESULTS.RESULT%TYPE;
V_RESULT_FORMAT VARCHAR2(100);
BEGIN
SELECT TEMPLATE INTO V_TEMPLATE FROM LOTTERYS WHERE ID = P_LOTTERY;
BEGIN
SELECT RESULT_FORMAT INTO V_RESULT_FORMAT FROM LOTTERYS WHERE ID = V_TEMPLATE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN;
END;
execute immediate '
CREATE PRIVATE TEMPORARY TABLE ORA$PTT_RESULT_VALIDATION (
results INT,
formats INT
)
ON COMMIT DROP DEFINITION ;
';
INSERT INTO ORA$PTT_RESULT_VALIDATION(results, formats)
select a.results, b.formats from (
select distinct rownum idx, regexp_substr(P_RESULT, '[^,]+', 1, LEVEL) results from dual
connect by regexp_substr(P_RESULT, '[^,]+', 1, level) is not null order by idx
) a full join
(
select distinct rownum idx, regexp_substr(V_RESULT_FORMAT, '[^,]+', 1, LEVEL) formats from dual
connect by regexp_substr(V_RESULT_FORMAT, '[^,]+', 1, level) is not null order by idx
) b on a.idx = b.idx order by b.idx;
begin
for i in (select * from ORA$PTT_RESULT_VALIDATION) loop
if REGEXP_COUNT(i.results, '\d') != i.formats then
commit;
RAISE_APPLICATION_ERROR (
num => -20000,
msg => 'Invalid Result Format');
end if;
end loop;
end;
commit;
END RESULT_VALIDATION;
is there any workaround that i can do something like this. or maybe not by the method of temporary table?
You can achieve it using the following query directly:
SQL> with template(result_format) as
2 (select '5,5,5,5,5,5,5,5,5,5,4,4,4,4,4,4,4,4,4,4,3,3,3,2,2,2,2' from dual),
3 dataa(p_result) as
4 (select '16768,74300,56212,38614,12250,52274,73018,32467,12618,48801,4257,6831,5436,4757,9395,5294,3687,3408,2803,1680,848,695,479,81,58,28,27' from dual)
5 SELECT
6 CASE
7 WHEN RES >= 1 THEN 'validation failed'
8 ELSE 'validation passed'
9 END AS FINAL_RESULT
10 FROM
11 (
12 SELECT
13 SUM(CASE
14 WHEN LENGTH(REGEXP_SUBSTR(P_RESULT, '[^,]+', 1, LEVEL)) <> REGEXP_SUBSTR(RESULT_FORMAT, '[^,]+', 1, LEVEL) THEN 1
15 ELSE 0
16 END) RES
17 FROM
18 DATAA D
19 CROSS JOIN TEMPLATE T
20 CONNECT BY
21 REGEXP_SUBSTR(P_RESULT, '[^,]+', 1, LEVEL) IS NOT NULL
22 );
FINAL_RESULT
-----------------
validation passed
Testing with values that fails. see first value in template, I have set it to 1 but its related value in dataa is 16768 (length: 5). So it must fail.
SQL> with template(result_format) as
2 (select '1,5,5,5,5,5,5,5,5,5,4,4,4,4,4,4,4,4,4,4,3,3,3,2,2,2,2' from dual),
3 dataa(p_result) as
4 (select '16768,74300,56212,38614,12250,52274,73018,32467,12618,48801,4257,6831,5436,4757,9395,5294,3687,3408,2803,1680,848,695,479,81,58,28,27' from dual)
5 SELECT
6 CASE
7 WHEN RES >= 1 THEN 'validation failed'
8 ELSE 'validation passed'
9 END AS FINAL_RESULT
10 FROM
11 (
12 SELECT
13 SUM(CASE
14 WHEN LENGTH(REGEXP_SUBSTR(P_RESULT, '[^,]+', 1, LEVEL)) <> REGEXP_SUBSTR(RESULT_FORMAT, '[^,]+', 1, LEVEL) THEN 1
15 ELSE 0
16 END) RES
17 FROM
18 DATAA D
19 CROSS JOIN TEMPLATE T
20 CONNECT BY
21 REGEXP_SUBSTR(P_RESULT, '[^,]+', 1, LEVEL) IS NOT NULL
22 );
FINAL_RESULT
-----------------
validation failed
SQL>
Note: This solution assumes that the number of values in both the strings is same.
Cheers!!

i have string 'Tprintthisstring' and want output into 'T,pri,ntt,his,str,ing' . can someone help me on this

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;

Oracle- Split string comma delimited (string contains spaces and consecutive commas)

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>