30. Januar 2012

XML Dokumente mit SQL vergleichen: XMLDIFF und XMLPATCH

Comparing XML with SQL: XMLDIFF and XMLPATCH
Heute möchte ich etwas über zwei ganz interessante SQL-Funktionen in der Datenbank schreiben: XMLDIFF und XMLPATCH. Fangen wir mit XMLDIFF an: Es vergleicht XML-Dokumente. Und dazu am besten mal ein Beispiel ...
<document xmlns="my-namespace">
 <leeres-xmltag></leeres-xmltag>
 <xmltag-inhalt>"</xmltag-inhalt>
</document>

<ns:document xmlns:ns="my-namespace">
 <ns:leeres-xmltag/>
 <ns:xmltag-inhalt>&quot;</ns:xmltag-inhalt>
</ns:document> 
Die Frage "Worin unterscheiden sich die beiden XML-Dokumente?" ist nur auf den ersten Blick einfach. Probieren wir die XMLDIFF-Funktion einfach mal aus ...
select xmldiff(
 xmltype('
  <document xmlns="my-namespace">
   <leeres-xmltag></leeres-xmltag>
   <xmltag-inhalt>"</xmltag-inhalt>
  </document>'
 ), xmltype('
  <ns:document xmlns:ns="my-namespace">
   <ns:leeres-xmltag/>
   <ns:xmltag-inhalt>&quot;</ns:xmltag-inhalt>
  </ns:document>'
 )
) from dual
/ 

XMLDIFF(XMLTYPE('<DOCUMENTXMLNS="MY-NAMESPACE"><LEERES-XMLTAG></LEERES-XMLTAG><X
--------------------------------------------------------------------------------
<xd:xdiff xsi:schemaLocation="http://xmlns.oracle.com/xdb/xdiff.xsd http://xmlns
.oracle.com/xdb/xdiff.xsd" xmlns:xd="http://xmlns.oracle.com/xdb/xdiff.xsd" xmln
s:ns="my-namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <?oracle-xmldiff operations-in-docorder="true" output-model="snapshot" diff-al
gorithm="global"?>
</xd:xdiff>
Die XMLDIFF-Funktion liefert zwar etwas zurück, bei genauer Betrachtung stellt man jedoch fest, dass das Tag xd:xdiff leer ist - es wurden also keine Unterschiede zwischen den XML-Dokumenten festgestellt - und das ist auch richtig so. Denn die im Text sichtbaren Unterschiede haben nach dem XML-Standard keine Bedeutung. Ein leeres Tag darf sowohl <tag></tag> als auch <tag/> geschrieben werden. Gleiches gilt für Zeichenentities oder Namensräume. XMLDIFF ist also zunächst eine sehr interessante Funktion, um überhaupt Unterschiede zwischen XML-Dokumenten zu erkennen. Doch XMLDIFF leistet noch mehr: Nehmen wir mal zwei XML-Dokumente, die sich tatsächlich unterscheiden ...
select xmldiff(
 xmltype('
  <document xmlns="my-namespace">
   <tag>Text 1</tag>
   <tag>Text 3</tag>
  </document>'
 ), xmltype('
  <ns:document xmlns:ns="my-namespace">
   <ns:tag>Text 1</ns:tag>
   <ns:tag>Text 2</ns:tag>
   <ns:tag option="a">Text 3</ns:tag>
  </ns:document>'
 )
) as xml_diff from dual
/ 

XML_DIFF
------------------------------------------------------------------------------------------
<xd:xdiff xsi:schemaLocation="http://xmlns.oracle.com/xdb/xdiff.xsd http://xmlns.oracle.co
m/xdb/xdiff.xsd" xmlns:xd="http://xmlns.oracle.com/xdb/xdiff.xsd" xmlns:ns="my-namespace"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <?oracle-xmldiff operations-in-docorder="true" output-model="snapshot" diff-algorithm="g
lobal"?>
  <xd:update-node xd:node-type="text" xd:xpath="/ns:document[1]/ns:tag[2]/text()[1]">
    <xd:content>Text 2</xd:content>
  </xd:update-node>
  <xd:append-node xd:node-type="element" xd:parent-xpath="/ns:document[1]">
    <xd:content>
      <ns:tag option="a">Text 3</ns:tag>
    </xd:content>
  </xd:append-node>
</xd:xdiff>
Wie man sehen kann, werden die Unterschiede nicht einfach nur aufgelistet: Wie sein Unix-Vorbild diff liefert auch XMLDIFF Anweisungen zurück, wie man das erste XML-Dokument in das zweite überführen kann. Und ebenso wie das Ergebnis eines Unix diff mit dem patch Werkzeug auf andere XML-Dokumente angewendet werden kann, kann auch das Ergebnis von XMLDIFF auf ein neues XML-Dokument mit Hilfe von XMLPATCH angewendet werden.
with diff as (
  -- Obiges SELECT XMLDIFF hier ...
)
select xmlpatch(
 xmltype('
  <document xmlns="my-namespace">
   <tag>Text a</tag>
   <tag>Text b</tag>
  </document>'
 ),
 xml_diff
) from diff
/ 
Die zuerst ermittelten XMLDIFF-Anweisungen ...
  • Ändere den Text des zweiten XML-Tags tag innerhalb von document auf Text 2
  • Hänge innerhalb des Tags document ein neues Tag tag mit dem Inhalt Text 3 und dem Attribut option="a" an.
... werden auf das im zweiten Parameter übergebene XML-Dokument angewendet. Das Ergebnis sieht in etwa wie folgt aus (die durch XMLPATCH ausgelösten Änderungen sind rot markiert):
XMLPATCH
------------------------------------------------------------------------------------------
<document xmlns="my-namespace">
  <tag>Text a</tag>
  <tag>Text 2</tag>
  <tag xmlns:ns="my-namespace" option="a">Text 3</tag>
</document>
Intern nutzt Oracle die Funktionen auch selbst - speziell für die in Place Schema Evolution der XML DB sind sie wichtig. Allerdings kann man sie auch selbst nutzen, wenn es darum geht, Änderungen an einem XML-Dokument zu erkennen und diese ggfs. auf andere Dokumente zu übertragen ...
Today I'd like to write something about two interesting SQL functions in the Oracle database: XMLDIFF and XMLPATCH. I'll start with XMLDIFF : As its name indicates - it compares XML documents. I'll illustrate this with an example ...
<document xmlns="my-namespace">
 <empty-xmltag></empty-xmltag>
 <xmltag-content>"</xmltag-content>
</document>

<ns:document xmlns:ns="my-namespace">
 <ns:empty-xmltag/>
 <ns:xmltag-content>&quot;</ns:xmltag-content>
</ns:document> 
What do you think? Are these XML documents equal ... or do they differ? This seems to be easy only at the first glance. But let's have XMLDIFF do the work ...
select xmldiff(
 xmltype('
  <document xmlns="my-namespace">
   <empty-xmltag></empty-xmltag>
   <xmltag-content>"</xmltag-content>
  </document>'
 ), xmltype('
  <ns:document xmlns:ns="my-namespace">
   <ns:empty-xmltag/>
   <ns:xmltag-content>&quot;</ns:xmltag-content>
  </ns:document>'
 )
) from dual
/ 

XMLDIFF(XMLTYPE('<DOCUMENTXMLNS="MY-NAMESPACE"><LEERES-XMLTAG></LEERES-XMLTAG><X
--------------------------------------------------------------------------------
<xd:xdiff xsi:schemaLocation="http://xmlns.oracle.com/xdb/xdiff.xsd http://xmlns
.oracle.com/xdb/xdiff.xsd" xmlns:xd="http://xmlns.oracle.com/xdb/xdiff.xsd" xmln
s:ns="my-namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <?oracle-xmldiff operations-in-docorder="true" output-model="snapshot" diff-al
gorithm="global"?>
</xd:xdiff>
The functions' output indicates that the documents are equal - from the XML point of view. And this makes sense since the XML standard allows different markup for the same content. Empty tags, for instance, can be expressed as a single tag with a trailing slash (<tag/>) as well as with an opening and a closing tag (<tag></tag>). Character entities like &quot; have the same meaning as the character itself ("). And there are more examples beyond these two. So comparing XML documents means more than just comparing the text contents - the XML standard must be kept in mind, and XMLDIFF does exactly this. In the next example we'll compare two documents which are different; also from the "XML point of view".
select xmldiff(
 xmltype('
  <document xmlns="my-namespace">
   <tag>Text 1</tag>
   <tag>Text 3</tag>
  </document>'
 ), xmltype('
  <ns:document xmlns:ns="my-namespace">
   <ns:tag>Text 1</ns:tag>
   <ns:tag>Text 2</ns:tag>
   <ns:tag option="a">Text 3</ns:tag>
  </ns:document>'
 )
) as xml_diff from dual
/ 

XML_DIFF
------------------------------------------------------------------------------------------
<xd:xdiff xsi:schemaLocation="http://xmlns.oracle.com/xdb/xdiff.xsd http://xmlns.oracle.co
m/xdb/xdiff.xsd" xmlns:xd="http://xmlns.oracle.com/xdb/xdiff.xsd" xmlns:ns="my-namespace"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <?oracle-xmldiff operations-in-docorder="true" output-model="snapshot" diff-algorithm="g
lobal"?>
  <xd:update-node xd:node-type="text" xd:xpath="/ns:document[1]/ns:tag[2]/text()[1]">
    <xd:content>Text 2</xd:content>
  </xd:update-node>
  <xd:append-node xd:node-type="element" xd:parent-xpath="/ns:document[1]">
    <xd:content>
      <ns:tag option="a">Text 3</ns:tag>
    </xd:content>
  </xd:append-node>
</xd:xdiff>
We can see, that XMLDIFF returns more than just the information that these two documents are different. As it's UNIX pendant diff it returns a "delta" - instructions how to modify the first document in order to get the second. This output can be consumed by XMLPATCH; as we'll see in the next example: We'll apply the "patch instruction" to another XML document ...
with diff as (
  -- Obiges SELECT XMLDIFF hier ...
)
select xmlpatch(
 xmltype('
  <document xmlns="my-namespace">
   <tag>Text a</tag>
   <tag>Text b</tag>
  </document>'
 ),
 xml_diff
) from diff
/ 
The "patching" instructions ...
  • Modify the text contents of the second tag tag within document to Text 2
  • Append another tag tag containing the text Text 3 and the attrbute option="a" within document.
... are now being applied to another XML document which is (in this case) even different from the very first one (which we used to compare initially). The output of the XMLPATCH operation is as follows ...
XMLPATCH
------------------------------------------------------------------------------------------
<document xmlns="my-namespace">
  <tag>Text a</tag>
  <tag>Text 2</tag>
  <tag xmlns:ns="my-namespace" option="a">Text 3</tag>
</document>
XMLDIFF and XMLPATCH are used internally by Oracle for the XML DB functionality of In Place XML Schema Evolution. But one can (as we have seen) use them also for own purposes - the functions are interesting when it's about comparing XML documents and computing deltas between them. einem XML-Dokument zu erkennen und diese ggfs. auf andere Dokumente zu übertragen ...

Keine Kommentare:

Beliebte Postings