Reputation: 135
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
Upvotes: 0
Views: 564
Reputation: 167981
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
Upvotes: 1
Reputation: 39477
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;
Upvotes: 2
Reputation: 191275
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.
Upvotes: 1