Question

I am using Oracle XE in version 11.2.

I have such kind of XML:

<root>
    <x a="a"/>
    <x a="b"/>
    <x a="c"/>
</root>

No I would like to add attribute b to each element x with value taken from sequence, but it should take new value for each element. Expected result is:

<root>
    <x a="a" b="1"/>
    <x a="b" b="2"/>
    <x a="c" b="3"/>
</root>

I've found that to add attribute to XML I can use insertchildxml but in is adding the same (frist) value from sequence to all attributes b. I can't find how to call this function for each individual element x.

I will be grateful for any help.

Was it helpful?

Solution

I've finaly found some solution and key to it was to use XMLTable() function. Here is my code:

declare
v_inXML xmltype;
  v_tmpXML xmltype;
  v_withIdXML xmltype;
  v_outXML xmltype;
BEGIN   
  v_inXML := XMLType('<root><x a="a"/><x a="b"/><x a="c"/></root>');
  v_withIdXML := XMLType('<root/>'); 
  v_outXML := XMLType('<root/>');

  for c_rec in (
    select *
    from   XMLTable('for $i in /root/x
          return $i'
          passing  v_inXML
          columns x xmltype path '/x'
    )
  )
  loop
    select insertchildxml(c_rec.x,'//x', '@b', pckg_ent_pk_seq.nextval) into v_tmpXML from dual;      
    select insertchildxml(v_withIdXML, '/root', 'x', v_tmpXML) into v_withIdXML from dual;    
  end loop;

  select updatexml(v_outXML, '/root', v_withIdXML) into v_outXML from dual;

  dbms_output.put_line(v_outXML.getClobVal());
END;

And the result is:

<root><x a="a" b="92"/><x a="b" b="93"/><x a="c" b="94"/></root>

OTHER TIPS

That can be done by various ways. Choice depends on how source data constructed, desired output format etc.

1. XSLT Transformation

Use XMLTransform() function to add attribute.

SQLFiddle test

  with params as (
    select 
      XMLParse( content
        '
        <root>
            <x a="a"/>
            <x a="b"/>
            <x a="c"/>
        </root>                  
        '
      ) as doc_field
    from dual
  )
  select
    XMLTransform( 
      doc_field,
      XMLParse( content
        ' 
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:template match="/">    
           <root>
           <xsl:for-each select="child::*">
             <xsl:apply-templates></xsl:apply-templates> 
           </xsl:for-each>
           </root>
        </xsl:template>
          <xsl:template match="x">
              <x  a="{@a}" b="{position()}" />
          </xsl:template>
        </xsl:stylesheet>
        '
      )
    )
  from params

2. XQuery returning single XML

Using XMLQuery() function single XML can be translated to another, basically same variant as above.

SQLFiddle test

with params as (
  select 
    XMLParse( content
      '
      <root>
          <x a="a"/>
          <x a="b"/>
          <x a="c"/>
      </root>                  
      '
    ) as doc_field
  from dual
)
select
  XMLQuery( 
    '
      <root>
      {
        for $x at $pos in $doc/root/x
        return <x a="{$x/@a}" b="{$pos}" />
      }
      </root>
    '
    passing doc_field as "doc"
    returning content
  )
from params

3. Get values from order of rows

This method better matches cases when XML must be constructed on the fly and sequence attribute value can be injected at construction phase.

SQLFiddle test

with params as (
  select 'a' a from dual union all
  select 'b' a from dual union all
  select 'c' a from dual 
)
select
  XMLElement("root",
    XMLAgg(
      XMLElement("x",
        XMLAttributes(
          a as "a",
          rownum as "b"
        )  
      )
    )
  )
from params

Last method also can help with already built XML if you break it to records with XMLTable() function.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top