Search code examples
sqloraclexpathextractxmltype

How Extract from XMLTYPE column, corresponding label for value in a tag within a value/label list from same XML


I got a sql from an Oracle table with a XMLTYPE column. The Xml has a tag with a value '2' and in the same xml there is the list with value/label pairs like this:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <characteristics>
        <motor>
            <fuel>2</fuel>
        </motor>
    </characteristics>
    <ValuesList>
        <CarValues>
            <CarValue>
                <value>1</value>
                <label>Diesel</label>
            </CarValue>
            <CarValue>
                <value>2</value>
                <label>Unleaded petrol</label>
            </CarValue>
        </CarValues>
    </ValuesList>
</Root>

With extract() or extractValue() functions it's easy to get the value for tag with any of these staments a, b

SELECT extract(t.xmlColumn, '//fuel/text()').getStringVal() a,
       extractValue(t.xmlColumn, '//fuel') b
  FROM Table t

The problem is I want to get the label text for value '2' from Valueslist 'Unleaded petrol'

I try to get that value with a XPATH expresion like this:

extractValue(t.xmlColumn, '//CarValue/label[../value=//fuel]')

The XPATH has been evaluated with Notepad XML Tools and it works, but there's no way to get any result. it's always null.

Any idea how to achive this? I don't know how to use XMLTABLE or any other solution to this case.


Solution

  • You need to include the /text() part in your attempt:

    SELECT
    extract(t.xmlColumn,'//fuel/text()').getStringVal() a
    ,extractValue(t.xmlColumn,'//fuel') b
    ,extractValue(t.xmlColumn, '//CarValue/label[../value=//fuel/text()]') c
    FROM your_table t
    
    A B C
    2 2 Unleaded petrol

    In your version you're trying to match a label with a value of the node <fuel>2</fuel>, not the text within than node, 2.

    Both extract() and extractValue() are deprecated, so you could use XMLQuery instead:

    SELECT
    XMLQuery('//fuel/text()' passing xmlColumn returning content) a
    ,XMLQuery('//CarValue/label[../value=//fuel/text()]/text()' passing xmlColumn returning content) a
    FROM your_table t
    
    A A
    2 Unleaded petrol

    Or with XMLTable();

    SELECT x.a, x.b
    FROM your_table t
    CROSS APPLY XMLTable(
      '/'
      passing t.xmlColumn
      columns a number path '//fuel',
        b varchar2(30) path '//CarValue/label[../value=//fuel/text()]'
    ) x
    
    A B
    2 Unleaded petrol

    fiddle


    If your version of Oracle isn't working with the node traversal using ../ then you could do it the hard way by getting the fuel value and all the id/label pairs with separate XMLTable calls, and then filtering those that match:

    SELECT x1.fuel, x2.label
    FROM your_table t
    CROSS JOIN XMLTable(
      '//fuel'
      passing t.xmlColumn
      columns fuel number path '.'
    ) x1
    JOIN XMLTable(
      '//CarValue'
      passing t.xmlColumn
      columns value number path 'value',
        label varchar2(30) path 'label'
    ) x2 ON x2.value = x1.fuel
    
    FUEL LABEL
    2 Unleaded petrol

    fiddle