user906153
user906153

Reputation: 1218

Concatinating XML data in sequence and then use an XPath to get out data

First off, my tables look like this..

Checkpoint_Data
---------------
Checkpoint_Id
Sequence_Number
Checkpoint_Data

Checkpoint
----------
Checkpoint_Id
Location
Transaction_Id

Transaction
-----------
Transaction_Id
Name
Time

Checkpoint_data contains broken up XML, and the sequence number determines how it fits together

I want to concatenate all of a Checkpoint's linked Checkpoint_Data together in sequence (1,2,..) and then use an XPath to extract some data that I want.

Here is the query I have right now, but it does not do any concatenation, and does not work correctly:

SELECT distinct(xmltype(data.trans_data).extract('//somePath/text()').getStringVal())  
FROM Checkpoint_Data cpd, Checkpoint cp, Transaction tr
WHERE cpd.checkpoint_id = cp.checkpoint_id
     and cp.location = 'someLocation'
     and cp.trans_id = tr.trans_id
     and tr.time >= to_date('3/26/2013:8:00 AM', 'mm/dd/yyyy:hh:miam')  
     and tr.time <= to_date('3/26/2013:11:00 AM', 'mm/dd/yyyy:hh:miam') 

How can I concatenate the broken up XML contained in Checkpoint_Data and then use an XPath to extract data from it?

Upvotes: 0

Views: 606

Answers (1)

DazzaL
DazzaL

Reputation: 21993

PLSQL approach:

SQL> create or replace function get_xml_doc(p_id Checkpoint_Data.checkpoint_id%type)
  2    return xmltype deterministic
  3  is
  4    v_xml clob;
  5  begin
  6    for r_row in (select cd.checkpoint_data
  7                    from Checkpoint_Data cd
  8                   where cd.checkpoint_id = p_id
  9                   order by cd.sequence_number)
 10    loop
 11      v_xml := v_xml || r_row.checkpoint_data;
 12    end loop;
 13
 14    return xmltype(v_xml);
 15  end get_xml_doc;
 16  /

Function created.

SQL>
SQL> select cp.checkpoint_id, get_xml_doc(cp.checkpoint_id) xml_doc
  2    from checkpoint cp, transaction tr
  3   where cp.location = 'someLocation'
  4     and cp.trans_id = tr.trans_id
  5     and tr.time <= sysdate
  6   group by cp.checkpoint_id;

CHECKPOINT_ID XML_DOC
------------- ----------------------------------------
            1 <root>
                <a>1st node</a>
                <b>2nd node</b>
                <compType>
                  <a>foo</a>
                </compType>
                <d>another node</d>
              </root>

or just SQL if PLSQL isn't an option:

you can use CDATA with XMLAGG eg :

SQL> select * from Checkpoint_Data;

CHECKPOINT_ID SEQUENCE_NUMBER CHECKPOINT_DATA
------------- --------------- --------------------------------------------------
            1               1 <root><a>1st node</a><b>2nd node</b>
            1               2 <compType><a>foo</a></compType><d>another node</d>
            1               3 </root>


SQL> select c.xmlstr.transform(xmltype('<xsl:stylesheet version="1.0"
  2          xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  3          <xsl:output method="xml" omit-xml-declaration = "yes" version="1.0" />
  4          <xsl:template match="/">
  5              <xsl:for-each select="/root/nde">
  6                  <xsl:sort select="@seq"/>
  7                  <xsl:value-of select="." disable-output-escaping="yes" />
  8              </xsl:for-each>
  9          </xsl:template>
 10      </xsl:stylesheet>')) xml
 11    from (SELECT cpd.checkpoint_id,xmlelement("root", xmlagg(
 12                 xmltype('<nde seq="' || cpd.sequence_number || '"><![CDATA['||cpd.checkpoint_data||']]></nde>'))) xmlstr
 13          FROM Checkpoint_Data cpd, Checkpoint cp, Transaction tr
 14          WHERE cpd.checkpoint_id = cp.checkpoint_id
 15               and cp.location = 'someLocation'
 16               and cp.trans_id = tr.trans_id
 17               and tr.time <= sysdate
 18           group by cpd.checkpoint_id) c;

XML
--------------------------------------------------------------------------------
<root><a>1st node</a><b>2nd node</b><compType><a>foo</a></compType><d>another no
de</d></root>

so i took each xml fragment and converted it to a CDATA node like:

<node seq="1"><!CDATA[<root><a>1st node</a><b>2nd node</b>]]></node>

then used XMLAGG to get these into one document like

<root>
  <node seq="1"><!CDATA[<root><a>1st node</a><b>2nd node</b>]]></node>
  <node seq="3"><!CDATA[</root>]]></node>
  <node seq="2"><!CDATA[<compType><a>foo</a></compType><d>another node</d>]]></node>
</root>

note: the sequences may be jumbed by the group by..so I applied an XSL to order them and print them (you could write your own aggregate to do this in order if you wanted or just write a pl/sql routine to get all the fragments and append them for you in order, if that's an option, you may prefer that).

NOTE: in 10g oracle seems to ignore disable-output-escaping (its ok in 11g). if you use 10g you can add a call to dbms_xmlgen.convert(clob, 1) to decode it.

select dbms_xmlgen.convert(
         xmlserialize(
           content c.xmlstr.transform(xmltype('<?xml version="1.0" encoding="ISO-8859-1"?>
    <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="xml" version="1.0" omit-xml-declaration = "yes"  encoding="UTF-8" indent="no"/>
        <xsl:template match="/">
            <xsl:for-each select="/root/node">
                <xsl:sort select="@seq"/>
                <xsl:value-of select="." disable-output-escaping="yes"/>
            </xsl:for-each>
        </xsl:template>
    </xsl:stylesheet>')) as clob), 1) xsl, xmlstr
  from (SELECT cpd.checkpoint_id,xmlelement("root", xmlagg(
               xmltype('<node seq="' || cpd.sequence_number || '"><![CDATA['||cpd.checkpoint_data||']]></node>'))) xmlstr
        FROM Checkpoint_Data cpd, Checkpoint cp, Transaction tr
        WHERE cpd.checkpoint_id = cp.checkpoint_id
             and cp.location = 'someLocation'
             and cp.trans_id = tr.trans_id
             and tr.time <= sysdate
         group by cpd.checkpoint_id) c;

fiddle: http://sqlfiddle.com/#!4/e1bf8/1

Upvotes: 1

Related Questions