mtndoe
mtndoe

Reputation: 444

Multiple comma separated lists to columns

I'm trying to split a comma separated list in a column to different rows. For example:

with testdata(vala,valb,valc) as(
select
    '1,2,3,4' as vala,
    '5,6,7,8' as valb,
    '9,10,11' as valc
from dual)

Using the table above I'm trying to get the values in such a form that vala,valb and valc are the columns, the different comma separated values are the rows. As shown in the image below:

Expected result

I already had a look into https://lalitkumarb.wordpress.com/2014/12/02/split-comma-delimited-string-into-rows-in-oracle/ and https://blogs.oracle.com/aramamoo/how-to-split-comma-separated-string-and-pass-to-in-clause-of-select-statement but I seem to be unable to apply the methods described as I have more columns...

The reason why I need to do this transformation is to use these values in a IN expression further down the query.

Upvotes: 0

Views: 309

Answers (3)

MT0
MT0

Reputation: 168623

A solution that only uses simple string functions (and not slow regular expressions), doesn't need any joins and can also handle multiple input rows:

Oracle Setup:

CREATE TABLE testdata (vala, valb, valc) AS
  SELECT '1,2,3,4','5,6,7,8', '9,10,11' FROM DUAL UNION ALL
  SELECT '12', '13,14,15,16', '' FROM DUAL;

Query:

WITH data ( vala, valb, valc, starta, startb, startc, enda, endb, endc, rn, idx ) AS (
  SELECT vala,
         valb,
         valc,
         1,
         1,
         1,
         INSTR( vala, ',', 1 ),
         INSTR( valb, ',', 1 ),
         INSTR( valc, ',', 1 ),
         ROWNUM,
         1
  FROM   testdata
UNION ALL
  SELECT vala,
         valb,
         valc,
         CASE WHEN enda = 0 THEN 0 ELSE enda + 1 END,
         CASE WHEN endb = 0 THEN 0 ELSE endb + 1 END,
         CASE WHEN endc = 0 THEN 0 ELSE endc + 1 END,
         CASE WHEN enda = 0 THEN 0 ELSE INSTR( vala, ',', enda + 1 ) END,
         CASE WHEN endb = 0 THEN 0 ELSE INSTR( valb, ',', endb + 1 ) END,
         CASE WHEN endc = 0 THEN 0 ELSE INSTR( valc, ',', endc + 1 ) END,
         rn,
         idx + 1
  FROM   data
  WHERE  enda > 0
  OR     endb > 0
  OR     endc > 0
)
SELECT CASE
       WHEN starta = 0 THEN NULL
       WHEN enda   = 0 THEN SUBSTR( vala, starta )
                       ELSE SUBSTR( vala, starta, enda - starta )
       END AS vala,
       CASE
       WHEN startb = 0 THEN NULL
       WHEN endb   = 0 THEN SUBSTR( valb, startb )
                       ELSE SUBSTR( valb, startb, endb - startb )
       END AS valb,
       CASE
       WHEN startc = 0 THEN NULL
       WHEN endc   = 0 THEN SUBSTR( valc, startc )
                       ELSE SUBSTR( valc, startc, endc - startc )
       END AS valc
FROM   data
ORDER BY rn, idx;

Output:

VALA | VALB | VALC
:--- | :--- | :---
1    | 5    | 9   
2    | 6    | 10  
3    | 7    | 11  
4    | 8    | null
12   | 13   | null
null | 14   | null
null | 15   | null
null | 16   | null

db<>fiddle here

Upvotes: 0

Ronnis
Ronnis

Reputation: 12843

This should work, regardless of the column "height".

with testdata(vala, valb, valc) as(
   select '1,2,3,4' as vala
         ,'5,6,7,8' as valb
         ,'9,10,11' as valc
     from dual
)
select regexp_substr(vala,'[^,]+', 1, level) as vala -- Pick the 
      ,regexp_substr(valb,'[^,]+', 1, level) as valb -- value at this 
      ,regexp_substr(valc,'[^,]+', 1, level) as valc -- position or null
  from testdata
  connect by regexp_substr(vala,'[^,]+', 1, level) is not null -- Generate one row if 
          or regexp_substr(valb,'[^,]+', 1, level) is not null -- any of the positions
          or regexp_substr(valc,'[^,]+', 1, level) is not null -- would have a value

Upvotes: 2

CompEng
CompEng

Reputation: 7394

try this:

with testdata(vala,valb,valc)
 as(
select
    '1,2,3,4' as vala,
    '5,6,7,8' as valb,
    '9,10,11' as valc
from dual)
,
main as (
select rownum rn, 
regexp_substr(vala,'[^,]+', 1, level) data from
testdata
connect by  regexp_substr(vala,'[^,]+', 1, level) is not null
),
sub as (
select  rownum rn, 
regexp_substr(valb,'[^,]+', 1, level)data from
testdata
connect by  regexp_substr(valb,'[^,]+', 1, level) is not null
),
sub2 as (
select  rownum rn, 
regexp_substr(valc,'[^,]+', 1, level)data from
testdata
connect by  regexp_substr(valc,'[^,]+', 1, level) is not null
)
select 
main.data,
sub.data,
sub2.data
 from
main 
full outer join
sub on main.rn=sub.rn
full outer join
sub2 on main.rn=sub2.rn

Upvotes: 1

Related Questions