简体   繁体   中英

Convert rows values into columns - oracle 11g

I'm extracting some data from XML type of fields, getting some values from specific tags, and I managed to gather them in a form of such results:

echo  | type| color             | active
-------------------------------------------
echo1 | car | yellow,green,blue | no,no,no
echo1 | car | yellow,green      | yes,yes
echo2 | car | green,blue,red    | no,no,no
echo2 | car | blue,red          | yes,yes
echo3 | car | yellow,green      | no,yes
...

Now I need to convert those results into this:

echo  | type| color  | active
------------------------------
echo1 | car | yellow | no
echo1 | car | green  | no
echo1 | car | blue   | no
echo1 | car | yellow | yes
echo1 | car | green  | yes
echo2 | car | green  | no
echo2 | car | blue   | no
echo2 | car | red    | no
echo2 | car | blue   | yes
echo2 | car | red    | yes
echo3 | car | yellow | no
echo3 | car | green  | yes
...

meaning, I need to split values from the 3rd and 4th columns at once. Each of the values from color column has a corresponding value in the active column and I need to match those pairs in records.

So having such results I use this:

SELECT  echo, type, trim(x.COLUMN_VALUE) color, trim(y.COLUMN_VALUE) active 
FROM 
( RESULTS )
, xmltable(('"' || REPLACE(color, ',', '","') || '"')) x, xmltable(('"' || REPLACE(active, ',', '","') || '"')) y

ORDER BY echo, type, color, active

It works correctly if I convert only one of those two columns at once, but when I want to convert 2 at once, it multiplies the records, as if multiplying values from column y by values from column x.

If in the last column there were only "yes" or only "no" values, then it would work to use the distinct in the select, it would eliminate the obsolete (not true) records. But with mixed values I'm struggling to do it.

Can anyone give me a tip how to fix it to not get obsolete records?

Thanks in advance!


Sample XML:

<rowCollection>
  <row>
    <column name="active">YES</column>
    <column name="customertype">Default</column>
    <column name="type">Default</column>
    <column name="risklevel">Default</column>
    <column name="color">yellow</column>
  </row>
  <row>
    <column name="active">YES</column>
    <column name="customertype">Default</column>
    <column name="type">Default</column>
    <column name="risklevel">Default</column>
    <column name="color">green</column>
  </row>
</rowCollection>

Snippet of query used to generate results:

xmlcast(
  xmlquery(
    'string-join(/rowCollection/row/column[5]/text(),",")'
    passing xml_field
    RETURNING CONTENT
  ) AS VARCHAR2(500)
) color,
xmlcast(
  xmlquery(
    'string-join(/rowCollection/row/column[1]/text(),",")'
    passing xml_field
    RETURNING CONTENT
  ) AS VARCHAR2(300)
) active

Use a recursive sub-query factoring clause and use INSTR to iteratively find the delimiters and then SUBSTR to extract the values:

Oracle Setup :

CREATE TABLE results ( echo, type, color, active ) AS
SELECT 'echo1', 'car', 'yellow,green,blue', 'no,no,no' FROM DUAL UNION ALL
SELECT 'echo1', 'car', 'yellow,green',      'yes,yes'  FROM DUAL UNION ALL
SELECT 'echo2', 'car', 'green,blue,red',    'no,no,no' FROM DUAL UNION ALL
SELECT 'echo2', 'car', 'blue,red',          'yes,yes'  FROM DUAL UNION ALL
SELECT 'echo3', 'car', 'yellow,green',      'no,yes'   FROM DUAL;

Query :

WITH lines ( echo, type, rn, idx, color, active, c_start, c_end, a_start, a_end ) AS (
  SELECT echo,
         type,
         ROW_NUMBER() OVER ( ORDER BY echo, type, color, active ),
         1,
         color,
         active,
         1,
         INSTR(color,',',1),
         1,
         INSTR(active,',',1)
  FROM   results
UNION ALL
  SELECT echo,
         type,
         rn,
         idx + 1,
         color,
         active,
         c_end + 1,
         INSTR(color,',',c_end + 1),
         a_end + 1,
         INSTR(active,',',a_end + 1)
  FROM   lines
  WHERE  c_end > 0
  AND    a_end > 0
)
SELECT echo,
       type,
       CASE c_end
       WHEN 0
       THEN SUBSTR( color, c_start )
       ELSE SUBSTR( color, c_start, c_end - c_start )
       END AS color,
       CASE a_end
       WHEN 0
       THEN SUBSTR( active, a_start )
       ELSE SUBSTR( active, a_start, a_end - a_start )
       END AS active
FROM   lines
ORDER BY rn, idx

Output :

\nECHO | TYPE | COLOR | ACTIVE\n:---- |  :--- |  :----- |  :----- \necho1 | car | yellow | yes   \necho1 | car | green | yes   \necho1 | car | yellow | no    \necho1 | car | green | no    \necho1 | car | blue | no    \necho2 | car | blue | yes   \necho2 | car | red | yes   \necho2 | car | green | no    \necho2 | car | blue | no    \necho2 | car | red | no    \necho3 | car | yellow | no    \necho3 | car | green | yes   \n

db<>fiddle here


Update

Don't aggregate and then split the delimited strings; just extract the values from the XML:

CREATE TABLE table_name ( xml_field ) AS
SELECT XMLTYPE( '<rowCollection>
  <row>
    <column name="active">YES</column>
    <column name="customertype">Default</column>
    <column name="type">Default</column>
    <column name="risklevel">Default</column>
    <column name="color">yellow</column>
  </row>
  <row>
    <column name="active">YES</column>
    <column name="customertype">Default</column>
    <column name="type">Default</column>
    <column name="risklevel">Default</column>
    <column name="color">green</column>
  </row>
</rowCollection>' ) FROM DUAL;

Query :

SELECT x.*
FROM   table_name t
       CROSS JOIN
       XMLTABLE(
         '/rowCollection/row'
         PASSING t.xml_field
         COLUMNS
           active VARCHAR2(3) PATH '/row/column[@name="active"]',
           type   VARCHAR2(10) PATH '/row/column[@name="type"]',
           color  VARCHAR2(10) PATH '/row/column[@name="color"]'
       ) x;

Output :

There will be a way to generate the echo column; its just this isn't included in your sample data.

\nACTIVE | TYPE | COLOR \n:----- |  :------ |  :----- \nYES | Default | yellow\nYES | Default | green \n

db<>fiddle here

You can use hierarchy query as following:

 SQL> SELECT ECHO, TYPE, 2 REGEXP_SUBSTR(COLOR, '[^,]+', 1, COLUMN_VALUE) AS COLOR, 3 REGEXP_SUBSTR(ACTIVE, '[^,]+', 1, COLUMN_VALUE) AS ACTIVE 4 FROM RESULTS 5 CROSS JOIN TABLE ( CAST(MULTISET( 6 SELECT LEVEL FROM DUAL 7 CONNECT BY LEVEL <= REGEXP_COUNT(COLOR, ',') + 1 8 ) AS SYS.ODCINUMBERLIST) ); ECHO TYP COLOR ACTIVE ----- --- ---------- -------------------------------- echo1 car yellow no echo1 car green no echo1 car blue no echo1 car yellow yes echo1 car green yes echo2 car green no echo2 car blue no echo2 car red no echo2 car blue yes echo2 car red yes echo3 car yellow no echo3 car green yes 12 rows selected. SQL>

Cheers!!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM