Splitting a comma separated string in Oracle not working - sql

I would like to retrieve a set of values from a comma separated string into an IN clause in a query. Does some one knows why the following code is not working properly;
WITH xtable AS (
SELECT 1 ID, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161,47222' AGT FROM DUAL
UNION ALL
SELECT 2 ID, '456,789' AGT FROM DUAL
UNION ALL
SELECT 3 ID, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161' AGT FROM DUAL
)
select regexp_substr(x.AGT,'[^,]+', 1, level)
from xtable x
where x.ID = 3
connect by regexp_substr(x.AGT, '[^,]+', 1, level) is not null;
In this scenario the result should be
AGT
1 116
2 117
3 169
4 170
5 173
6 175
7 9015
8 44008
9 44367
10 44446
11 45081
12 45083
13 46779
14 47161
Instead I get an almost infinite loop of the same values

Issue in your query is that the where clause will be applied to only level 1 not any further.
Try this using nested table:
WITH xtable AS (
SELECT 1 ID, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161,47222' AGT FROM DUAL
UNION ALL
SELECT 2 ID, '456,789' AGT FROM DUAL
UNION ALL
SELECT 3 ID, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161' AGT FROM DUAL
)
select regexp_substr(x.AGT,'[^,]+', 1, t.column_value) agt
from xtable x cross join table(
cast(
multiset(
select level
from dual
connect by level <= regexp_count(x.AGT,',') + 1
)as sys.odcinumberlist
)
) t
where x.id = 3;
It's a general purpose query which you can use even without where clause if you wanted to convert all of the them at once.
In Oracle 12c+, you can use OUTER APPLY to achieve the same effect and simpler syntax:
WITH xtable AS (
SELECT 1 ID, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161,47222' AGT FROM DUAL
UNION ALL
SELECT 2 ID, '456,789' AGT FROM DUAL
UNION ALL
SELECT 3 ID, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161' AGT FROM DUAL
)
select regexp_substr(x.AGT,'[^,]+', 1, t.n) agt
from xtable x
outer apply (
select level n
from dual
connect by level <= regexp_count(x.AGT,',') + 1
) t
where x.id = 3;

You have the same values in multiple rows, so your connect-by is bouncing between them. Hierarchical queries are a little tricky with multiple rows involved. If you really only want the values for a single ID you can filter in a subquery:
WITH xtable AS (
SELECT 1 ID, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161,47222' AGT FROM DUAL
UNION ALL
SELECT 2 ID, '456,789' AGT FROM DUAL
UNION ALL
SELECT 3 ID, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161' AGT FROM DUAL
)
select regexp_substr(x.AGT,'[^,]+', 1, level)
from (select AGT from xtable where ID = 3) x
connect by regexp_substr(x.AGT, '[^,]+', 1, level) is not null;
REGEXP_SUBSTR(X.AGT,'[^,]+',1,LEVEL)
----------------------------------------------------------------------------
116
117
169
170
173
175
9015
44008
44367
44446
45081
45083
46779
47161
14 rows selected.
Or you can include id = prior id, but then also need to use a non-deterministic function call to prevent cycling:
WITH ... (...)
select regexp_substr(x.AGT,'[^,]+', 1, level)
from xtable x
where x.ID = 3
connect by id = prior id and prior dbms_random.value is not null
and regexp_substr(x.AGT, '[^,]+', 1, level) is not null;
which gets the same 14 rows back, and would work if you included multiple IDs.

There are multiple solutions for this in the Splitting Delimited Strings - SO Oracle Documentation page.
A solution using a correlated hierarchical query is:
WITH xtable (id, agt ) AS (
SELECT 1, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161,47222' FROM DUAL
UNION ALL
SELECT 2, '456,789' FROM DUAL
UNION ALL
SELECT 3, '116,117,169,170,173,175,9015,44008,44367,44446,45081,45083,46779,47161' FROM DUAL
)
SELECT id,
REGEXP_SUBSTR( x.agt, '[^,]+', 1, t.COLUMN_VALUE ) AS item
FROM xtable x
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( x.agt, '[^,]+' )
)
) AS SYS.ODCINUMBERLIST
) t
WHERE x.id = 3;
Output:
ID ITEM
-- -----
3 116
3 117
3 169
3 170
3 173
3 175
3 9015
3 44008
3 44367
3 44446
3 45081
3 45083
3 46779
3 47161

Related

Monitoring data with delimiter to create a new rows

I want to create a rows from one row on table that contains a delimiter on some fields as mentioned below on screen shoot
I want a result A separalte rows for the rows that already contains a deleimiter ;
the inputs are data from table 1 and the output is data as mentionned below on table 2 using oracle sql :insert and select query
you can see below the output recommanded:
One method is a recursive CTE:
with cte(id, description, val, before, after, n, cnt) as (
select id, description, val, before, after, 1 as n, regexp_count(description, ';')
from t
union all
select id, description, val, before, after, n + 1, cnt
from cte
where n <= cnt
)
select id,
regexp_substr(description, '[^;]+', 1, n) as description,
regexp_substr(val, '[^;]+', 1, n) as val,
regexp_substr(before, '[^;]+', 1, n) as before,
regexp_substr(after, '[^;]+', 1, n) as after
from cte;
Here is a db<>fiddle.
Alternatively:
SQL> with test (id, description, val, after, before) as
2 -- you already have sample data and don't type this
3 (select 1, 'ARTIC1;ARTIC2;ART11', '15;2;3', '12;6;8', '13;7;12' from dual union all
4 select 2, 'ARTICLE3;ARTICLE4' , '3;5' , '10;23' , '12;25' from dual union all
5 select 3, 'ARTICLE 5' , '6' , '2' , '1.9' from dual
6 )
7 -- query that does the job begins here
8 select id,
9 regexp_substr(description, '[^;]+', 1, column_value) descr,
10 regexp_substr(val , '[^;]+', 1, column_value) val,
11 regexp_substr(after , '[^;]+', 1, column_value) after,
12 regexp_substr(before , '[^;]+', 1, column_value) before
13 from test cross join
14 table(cast(multiset(select level from dual
15 connect by level <= regexp_count(description, ';') + 1
16 ) as sys.odcinumberlist))
17 order by id, descr, val;
ID DESCR VAL AFTER BEFORE
---------- ---------- ---------- ---------- ----------
1 ARTIC1 15 12 13
1 ARTIC2 2 6 7
1 ART11 3 8 12
2 ARTICLE3 3 10 12
2 ARTICLE4 5 23 25
3 ARTICLE 5 6 2 1.9
6 rows selected.
SQL>

Oracle - how to replace and sum from the output?

I have output of group by as below.
SELECT param2,count(*) FROM table WHERE CALLED='1234' GROUP BY PARAM2
Param2 count(*)
135; 616
135;135; 18
135;135;135; 4
135;135;135;135; 2
135;135;135;135;135;135;27; 15
135;135;135;27; 5
136; 43
136;136; 383
136;136;136; 47
136;136;136;27; 32
Expected: For all the output param2 that includes 135 or 136 should be group by once again such that result should be as below:
Param2 count(*)
135 660
136 505
For the given example, a simple straight-forward solution would do the job. Not sure if this is as general as necessary:
select '135' as "Param2", count(*) from table
where called = '1234' and ';'||param2 like '%;135;%'
union all
select '136' as "Param2", count(*) from table
where called = '1234' and ';'||param2 like '%;136;%'
Try below query,
Keeping my base source as the output you have mentioned ,we can use traditional regular expression to split the row and get distinct values present for param2 and then a sum over it would give the result as expected. I am little skeptical about how the performance will be but you can give it a try.
with sample data:
with table1
as
(
select '135;' param2, 616 cnt from dual union all
select '135;135;', 18 from dual union all
select '135;135;135;', 4 from dual union all
select '135;135;135;135;', 2 from dual union all
select '135;135;135;135;135;135;27;', 15 from dual union all
select '135;135;135;27;', 5 from dual union all
select '136;', 43 from dual union all
select '136;136;', 383 from dual union all
select '136;136;136;', 47 from dual union all
select '136;136;136;27;', 32 from dual
)
select split,sum(cnt) cnt
from
(select distinct t1.*, regexp_substr(param2,'[^;]+', 1, level) split
from table1 t1;
connect by regexp_substr(param2, '[^;]+', 1, level) is not null)
group by split;
So the final query should be ,
with table1
as
(
select param2, count(*) cnt
from table
where called = '1234'
group by param2
)
select split as param2,sum(cnt) cnt
from
(select distinct t1.*, regexp_substr(param2,'[^;]+', 1, level) split
from table1 t1;
connect by regexp_substr(param2, '[^;]+', 1, level) is not null)
group by split;

Usage of combinatorics in Oracle SQL

Could use some help or insights, cause I'm going nuts..
Situation: I have a table of players ID's, with values 1,2,3.. to 7.
Objective: Want to create a roster of 4 players, from available players (in our case there are 7 of them). Its a classical combinatoric task, we need to calculate C(k,n). In our case C(4,7)=840/24=35. So, there are possible 35 ways to build a roster. I want to create a table of rosters with player ID's. Here's the current script, that builds the current roster table:
with comb_tbl as(
select
tmp_out.row_num,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,1) plr_id_1,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,2) plr_id_2,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,3) plr_id_3,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,4) plr_id_4
from(
select
rownum row_num,
substr(tmp.combinations,2) comb_sets
from(
select
sys_connect_by_path(plr.plr_id, ',') combinations
from(
select 1 plr_id from dual union
select 2 plr_id from dual union
select 3 plr_id from dual union
select 4 plr_id from dual union
select 5 plr_id from dual union
select 6 plr_id from dual union
select 7 plr_id from dual) plr
connect by nocycle prior plr.plr_id != plr.plr_id) tmp
where
length(substr(tmp.combinations,2)) = 7) tmp_out)
select
tmp1.*
from
comb_tbl tmp1
Problem:Its creates 840 possibilities, but I need to remove the "identical" ones, for example roster (1,2,3,4) is "identical" to roster (2,1,3,4). Any insights/comments/critics are welcome. Maybe the approach itself is wrong?
There is a 1-1 correspondence between possible rosters and ORDERED subsets of four elements of the seven-element set.
In your CONNECT BY clause, you only check that the player id's be different, rather than that they be in increasing order. Change != to < in CONNECT BY and it will work. (Also, you won't need NOCYCLE anymore.)
This could be a join way:
with plr(plr_id) as
( select level from dual connect by level <= 7)
select p1.plr_id, p2.plr_id, p3.plr_id, p4.plr_id
from plr p1
inner join plr p2
on(p1.plr_id < p2.plr_id)
inner join plr p3
on(p2.plr_id < p3.plr_id)
inner join plr p4
on(p3.plr_id < p4.plr_id)
For example, with n=5 you would have:
PLR_ID PLR_ID PLR_ID PLR_ID
---------- ---------- ---------- ----------
1 2 3 4
1 2 3 5
1 2 4 5
1 3 4 5
2 3 4 5
This is not an answer; it's merely a continuation of my earlier comment:
By making those additional changes, your query could end up something like:
SELECT regexp_substr(tmp_out.comb_sets,'[^,]+',1,1) plr_id_1,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,2) plr_id_2,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,3) plr_id_3,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,4) plr_id_4
FROM (SELECT sys_connect_by_path(plr.plr_id, ',') comb_sets,
LEVEL lvl
FROM (select 1 plr_id from dual union all
select 2 plr_id from dual union all
select 3 plr_id from dual union all
select 4 plr_id from dual union all
select 5 plr_id from dual union all
select 6 plr_id from dual union all
select 7 plr_id from dual) plr
CONNECT BY PRIOR plr.plr_id < plr.plr_id
AND LEVEL <= 4) tmp_out
WHERE lvl = 4;
which is, IMO, easier to read and understand than your original query.

Oracle Regex Connect By

I am trying to produce multiple rows after performing a regex on a column splitting all values in square brackets. I'm only able to return a single value though, currently.
The field I am performing the regex has this value:
[1265]*[1263]
I am trying to get 1265 and 1263 in my result set as separate rows.
SELECT REGEXP_SUBSTR(column,'\[(.*?)\]',1,LEVEL) AS "col1"
FROM table
CONNECT BY REGEXP_SUBSTR(column,'\[(.*?)\]',1,LEVEL) IS NOT NULL;
Instead I just get this in the result set.
[1263]
with test (rn, col) as
(
select 'a', '[123]*[abc] []' from dual union all
select 'b', '[45][def] ' from dual union all
select 'c', '[678],.*' from dual
),
coll (rn, col) as
(
select rn,regexp_replace(col, '(\[.*?\])|.', '\1') from test
),
cte (rn, cnt, col, i) as
(
select rn, 1, col, regexp_substr(col, '(\[(.*?)\])', 1, 1, null, 2)
from coll
union all
select rn, cnt+1, col, regexp_substr(col, '(\[(.*?)\])', 1, cnt+1, null, 2)
from cte
where cnt+1 <= regexp_count(col, '\[.*?\]')
)
select * from cte
order by 1,2;
This regex counts elements by looking for closing brackets and returns the digits inside the brackets, allowing for NULLs. Separators are ignored since the data elements you want are surrounded by square brackets we can focus on those.
SQL> with test(rownbr, col) as (
select 1, '[1265]**[1263]' from dual union
select 2, '[123]' from dual union
select 3, '[111][222]*[333]' from dual union
select 4, '[411]*[][433]' from dual
)
select distinct rownbr, level as element,
regexp_substr(col, '\[([0-9]*)\]', 1, level, null, 1) value
from test
connect by level <= regexp_count(col, ']')
order by rownbr, element;
ROWNBR ELEMENT VALUE
---------- ---------- -----
1 1 1265
1 2 1263
2 1 123
3 1 111
3 2 222
3 3 333
4 1 411
4 2
4 3 433
9 rows selected.
SQL>

Oracle SQL : Regexp_substr

I have below sample values in a column
Abc-123-xyz
Def-456-uvw
Ghi-879-rst-123
Jkl-abc
Expected output is the third element split by '-', in case there is no third element, the last element will be retrieve.
See expected output below:
Xyz
Uvw
Rst
Abc
Thanks ahead for the help.
SELECT initcap(nvl(regexp_substr(word, '[^-]+', 1,3),regexp_substr(word, '[^-]+', 1,2))) FROM your_table;
Another approach:
SQL> with t1(col) as(
2 select 'Abc-123-xyz' from dual union all
3 select 'Def-456-uvw' from dual union all
4 select 'Ghi-879-rst-123' from dual union all
5 select 'Jkl-Abc' from dual
6 )
7 select regexp_substr( col
8 , '[^-]+'
9 , 1
10 , case
11 when regexp_count(col, '[^-]+') >= 3
12 then 3
13 else regexp_count(col, '[^-]+')
14 end
15 ) as res
16 from t1
17 ;
Result:
RES
---------------
xyz
uvw
rst
Abc
regexp_substr(column, '(.*?-){0,2}([^-]+)', 1, 1, '', 2)
You can also do it without RegEx:
with t1 as(
select 'Abc-123-xyz' as MyText from dual union all
select 'Def-456-uvw' from dual union all
select 'Ghi-879-rst-123' from dual union all
select 'Jkl-Abc' from dual
)
SELECT
SUBSTR(t1.mytext, LENGTH(t1.mytext) - INSTR(REVERSE(t1.mytext), '-') + 2)
FROM t1
;