Work Around for PL/SQL to do column validation - sql

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

Related

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

Oracle function to compare strings in a not ordered way

I need a function to make a comparison between two strings withouth considering the order in oracle.
i.e. "asd" and "sad" should be considered as equal.
Are there similar functions? Or I need to write my own function?
This can be done with a simple java function to sort the characters of a string alphabetically:
CREATE AND COMPILE JAVA SOURCE NAMED SORTSTRING AS
public class SortString {
public static String sort( final String value )
{
final char[] chars = value.toCharArray();
java.util.Arrays.sort( chars );
return new String( chars );
}
};
/
Which you can then create a PL/SQL function to invoke:
CREATE FUNCTION SORTSTRING( in_value IN VARCHAR2 ) RETURN VARCHAR2
AS LANGUAGE JAVA NAME 'SortString.sort( java.lang.String ) return java.lang.String';
/
Then you can do a simple comparison on the sorted strings:
SELECT CASE
WHEN SORTSTRING( 'ads' ) = SORTSTRING( 'das' )
THEN 'Equal'
ELSE 'Not Equal'
END
FROM DUAL;
Not exactly a rocket science, but works (kind of, at least on simple cases).
What does it do? Alphabetically sorts letters in every string and compares them.
SQL> with test (col1, col2) as
2 (select 'asd', 'sad' from dual),
3 inter as
4 (select
5 col1, regexp_substr(col1, '[^.]', 1, level) c1,
6 col2, regexp_substr(col2, '[^.]', 1, level) c2
7 from test
8 connect by level <= greatest(length(col1), length(col2))
9 ),
10 agg as
11 (select listagg(c1, '') within group (order by c1) col1_new,
12 listagg(c2, '') within group (order by c2) col2_new
13 from inter
14 )
15 select case when col1_new = col2_new then 'Equal'
16 else 'Different'
17 end result
18 From agg;
RESULT
---------
Equal
SQL> with test (col1, col2) as
2 (select 'asd', 'sadx' from dual),
<snip>
RESULT
---------
Different
SQL>
Yet another solution, using the SUBSTR function and CONNECT BY loop.
SQL Fiddle
Query 1:
WITH a
AS (SELECT ROWNUM rn, a1.*
FROM ( SELECT SUBSTR ('2asd', LEVEL, 1) s1
FROM DUAL
CONNECT BY LEVEL <= LENGTH ('2asd')
ORDER BY s1) a1),
b
AS (SELECT ROWNUM rn, a2.*
FROM ( SELECT SUBSTR ('asd2', LEVEL, 1) s2
FROM DUAL
CONNECT BY LEVEL <= LENGTH ('asd2')
ORDER BY s2) a2)
SELECT CASE COUNT (NULLIF (s1, s2)) WHEN 0 THEN 'EQUAL' ELSE 'NOT EQUAL' END
res
FROM a INNER JOIN b ON a.rn = b.rn
Results:
| RES |
|-------|
| EQUAL |
EDIT : A PL/SQL Sort function for alphanumeric strings.
CREATE OR replace FUNCTION fn_sort(str VARCHAR2)
RETURN VARCHAR2 DETERMINISTIC AS
v_s VARCHAR2(4000);
BEGIN
SELECT LISTAGG(substr(str, LEVEL, 1), '')
within GROUP ( ORDER BY substr(str, LEVEL, 1) )
INTO v_s
FROM dual
CONNECT BY LEVEL < = length(str);
RETURN v_s;
END;
/
select fn_sort('shSdf3213Js') as s
from dual;
| S |
|-------------|
| 1233JSdfhss |
In case you want to create your own sort function, you can use below code,
CREATE OR REPLACE FUNCTION sort_text (p_text_to_sort VARCHAR2) RETURN VARCHAR2
IS
v_sorted_text VARCHAR2(1000);
BEGIN
v_sorted_text := p_text_to_sort;
FOR i IN 1..LENGTH(p_text_to_sort)
LOOP
FOR j IN 1..LENGTH(p_text_to_sort)
LOOP
IF SUBSTR(v_sorted_text, j, 1)||'' > SUBSTR(v_sorted_text, j+1, 1)||'' THEN
v_sorted_text := SUBSTR(v_sorted_text, 1, j-1)||
SUBSTR(v_sorted_text, j+1, 1)||
SUBSTR(v_sorted_text, j, 1)||
SUBSTR(v_sorted_text, j+2);
END IF;
END LOOP;
END LOOP;
RETURN v_sorted_text;
END;
/
SELECT SORT_TEXT('zlkdsadfsdfasdf') SORTED_TEXT
FROM dual;
SORTED_TEXT
---------------
aaddddfffklsssz

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>

my table contain data in below format and does not contain any automated primary key [duplicate]

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;

Splitting comma separated values in Oracle

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;