Der Datentyp »XMLType«

(Auszug aus "Oracle PL/SQL: Das umfassende Handbuch", Kapitel 16 "Arbeiten mit XML", von Jürgen Sieben)

Wir beginnen unseren Rundgang durch die XML-Unterstützung von Oracle beim Datentyp XMLType. Dieser Datentyp ist ein objektorientierter Datentyp, dessen Implementierung in C ausgeführt ist. Als objektorientierter Datentyp kann XMLType dazu genutzt werden, Tabellenspalten zu definieren. Es können aber auch Tabellen direkt von diesem Typ abgeleitet werden.

Verwendung von »XMLType« als Tabellen- oder Spaltentyp

Sehen wir uns für beide Anwendungsfälle ein Beispiel an:

create table xml_test_1(id number, xml_content XMLType);


Tabelle wurde erstellt.

In diesem Beispiel ist die Tabellenspalte xml_content vom Typ XMLType. Zunächst ist mit diesem Typ keine besondere Speicherung verbunden, die Daten werden technisch gesehen in einer CLOB-Spalte abgelegt. Allerdings »weiß« Oracle mehr über diese Daten, denn der Zugriff auf die XML-Strukturen ist über spezielle, im Datentyp definierte Typmethoden sichergestellt. Da es sich um XML handelt, kann Oracle in diesem Datentyp mit Mitteln der XML-Verarbeitung suchen, Teile filtern oder ganze XMLType-Instanzen durch XSLT umformen lassen. Im folgenden Beispiel wird eine Tabelle als objektorientierte Tabelle vom Typ XMLType angelegt:

create table xml_test_2 of xmltype;


Tabelle wurde erstellt.

Diese Anweisung mag Ihnen obskur vorkommen, wenn Sie noch nicht mit den objektorientierten Typen gearbeitet haben. Lassen Sie uns die Tabelle einmal beschreiben, um zu sehen, wie sie sich im Data Dictionary darstellt:

desc xml_test_2;


Name                       Null?    Typ
-------------------------- -------- -----------------------
                                    TABLE of PUBLIC.XMLTYPE

Das ist noch nicht besonders erhellend. Um Näheres über diesen Tabellentyp zu erfahren, fügen wir eine XML-Instanz ein. Da es sich um einen objektorientierten Typ handelt, müssen wir zunächst eine Instanz dieses Typs erzeugen. Dafür benutzen wir eine Konstruktorfunktion dieses Typs (die daher ebenso heißt wie der Typ selbst) und übergeben ihr eine Zeichenkette, die im Format XML formatiert ist:

insert into xml_test_2
  values (XMLType('<Foo/>'));


1 Zeile wurde erstellt.

Diese nun wirklich einfache und kurze XML-Instanz ist also offensichtlich in die Tabelle eingefügt worden. Was ist zu sehen, wenn wir nun diese Zeile wieder aus der Datenbank auslesen?

select *
  from xml_test_2;


SYS_NC_ROWINFO$
-----------------------
<Foo/>

Code-Beispiel: Verschiedene Varianten der Erzeugung von» XMLType«-Tabellen und -Spalten

Offensichtlich ist für diese Tabelle ein vom System erzeugter Spaltenname SY_NC_ROWINFO$ erzeugt worden (wie hübsch – und so gut zu merken!). Wir kennen ein solches Verhalten schon von der Funktion table(), mit deren Hilfe wir das Ergebnis einer Tabellenfunktion (oder eines Cursors) als reguläre, durch SQL ansprechbare Tabelle umformen konnten. In diesem Fall war der Spaltenname, den Oracle erzeugt hat, COLUMN_VALUE. Analog hat Oracle im Übrigen auch für diesen Spaltenbezeichner oben ein Alias vereinbart, das OBJECT_VALUE heißt und synonym zu SYS_NC_ROWNIFO$ genutzt werden kann. Außerdem ist die von uns eingefügte XML-Instanz wieder ausgeliefert worden. Ähnlich würde sich auch unsere erste XML-Tabelle verhalten, in der XMLType als Spaltentyp vereinbart wurde. Mit einer solchen XMLType-Spalte arbeiten Sie daher so, wie Sie das auch von skalaren Datentypen gewohnt sind:

insert into xml_test_1
  values (1, XMLType('<Foo/>'));


1 Zeile wurde erstellt.
select *
  from xml_test_1;


        ID XML_CONTENT
---------- ------------
        1 <Foo/>

Code-Beispiel: Einfügen einer XML-Instanz in eine Tabelle

In dieser Variante ist der Datentyp also genauso zu verwenden wie jeder andere Datentyp auch, wenn wir einmal davon absehen, dass wir, wir bei allen objektorientierten Typen, einen Konstruktor benötigen. Doch worin liegt nun die Stärke dieses Datentyps? Derzeit scheint es ja keinen Unterschied zwischen diesem Datentyp und einer Zeichenkette zu geben. Die Gründe, XMLType als Datentyp zu verwenden, sind damit noch nicht offensichtlich. Lassen Sie uns eine etwas umfangreichere XML-Instanz in die Datenbank speichern und anschließend sehen, welche Möglichkeiten dadurch für uns entstehen:

insert into xml_test_1
values (1, XMLType('<Mitarbeiter id="12345"><Name>Müller</Name></Mitarbeiter>'));


1 Zeile wurde erstellt.

Mit dieser Anweisung haben wir eine XML-Instanz in die Datenbank eingefügt, die ein Mitarbeiter-Element mit einem Attribut id und einem Kindelement Name umfasst. Diese leicht komplexere XML-Instanz bietet uns nun die Gelegenheit, die Stärke des Datenformats zu demonstrieren. Stellen wir uns vor, wir benötigten aus diesen Daten den Wert des Attributs id. Nun scheint es in diesem einfachen Beispiel nicht sehr schwierig zu sein, dieses Attribut, vielleicht mit einem regulären Ausdruck, mit herkömmlichen Textwerkzeugen zu isolieren, doch können wir uns leicht vorstellen, dass eine solche Aufgabe bei umfangreicheren Dateien zu großen Schwierigkeiten führte. Stattdessen verwenden wir eine sehr seltsam aussehende Syntax:

select x.xml_content.extract('/Mitarbeiter/@id').getnumberval() id
from xml_test_1 x


        ID
----------
     12345

Code-Beispiel: Oracle weiß, wie XML strukturiert ist.

Das Beispiel zeigt, dass wir auf die Spalte xml_content mittels eines Tabellenalias x zugreifen. Ein solches Tabellenalias ist für Zugriffe auf Objekte in der Datenbank erforderlich. Mithilfe dieses »Zeigers« können wir nun auf den objektorientierten Typ zugreifen und eine seiner Typfunktionen aufrufen: die Funktion extract. Als Parameter übergeben wir dieser Funktion einen Ausdruck in XPath, um innerhalb einer XML-Instanz zu navigieren. Wir wenden hier also keine Oracle-Technologie an, sondern einen offiziellen XML-Standard. Mithilfe der Notation @id verzweigen wir aus der Hierarchie der Elemente zu dem Attribut id, das die von uns gewünschte Information enthält. Die Funktion liefert eine Zeichenkette mit dem Inhalt des Attributs id zurück und übergibt diese Zeichenkette an die Typfunktion getNumberVal(). Diese Funktion wandelt die Zeichenkette in eine Zahl um und gibt diese aus.

Neuerungen in Version 12c
Bevor Sie sich dieses Vorgehen allzu sehr aneignen: Die Nutzung der XMLType-Funktionen ist in Version 12c der Datenbank als deprecated eingestuft – zugunsten der XQuery-Funktionen XMLExtract und XMLTable, die wir unter Relationale Daten aus XML extrahieren besprechen werden.

»XMLType«-Member Functions

Wie wir gesehen haben, umfasst der Datentyp XMLType eine Reihe Methoden, sogenannte Member Functions. Einen Überblick über die zur Verfügung stehenden Methoden können Sie sich mit der Anweisung

desc XMLType

ansehen. Sortieren wir die Liste dieser Funktionen ein wenig.

Konstruktorfunktionen und Funktionen zur Erzeugung von XML

Zunächst sind dort Konstruktorfunktionen und Funktionen zur Erzeugung von XML zu nennen. Dieser Bereich der Funktionen umfasst zunächst alle Überladungen mit gleichem Namen wie der Datentyp. Es stehen Überladungen für varchar2, CLOB, BLOB, bfile, für Cursor-Variablen und den Datentyp anydata und sogar für undefined zur Verfügung, der genutzt wird, um ein Objekt in XML zu überführen. Sie können also beinahe alles mit der Konstruktorfunktion XMLType() zu XML machen, solange es inhaltlich irgendwie als XML durchgeht. Wenn Sie sich die Deklaration dieser Konstruktorfunktionen ansehen, stellen Sie fest, dass diese Funktionen alle sich selbst (self) als Ergebnis zurückgeben. Das ist der schon besprochene Zeiger auf die Objektinstanz, mit dessen Hilfe der Zugriff auf die Instanz dieses Datentyps organisiert wird.

Dann existieren noch die Funktionen createXml mit ihren Überladungen. Diese Funktionen sind mehr oder minder Dopplungen der Konstruktormethoden. Sie erkennen, dass diese Methoden static deklariert sind, was bedeutet, dass diese Funktionen genutzt werden können, ohne vorher eine Instanz des Typs erzeugen zu müssen. Das ist auch besser so, schließlich sollen diese Funktionen ja gerade diese Instanz erzeugen. In der Praxis sind diese Funktionen seltener im Einsatz, normalerweise werden einfach die Konstruktormethoden verwendet. Ich sehe diesen Ansatz da und dort in Kombination mit der Umformung von Objekttypen nach XML.

Methoden zur Analyse der XML-Instanz

In diese Gruppe fallen Methoden, die Auskunft über den Zustand der XML-Instanz geben, die in der Instanz gespeichert ist. Hier finden Sie Funktionen wie isSchemaValidated() oder isFragment() oder isSchemaValid(), aber auch Funktionen, die zum Beispiel das Wurzelelement liefern oder den Namen des Schemas, das sie validiert. Diese Funktionen sind für die Wartung, zum Teil auch für die Programmierung von Triggern, wichtig. Eine wesentliche Funktion dieser Gruppe ist auch die Funktion existsNode(), die überprüft, ob die Instanz ein gesuchtes Element enthält.

Methoden zur Bearbeitung der XML-Instanz

Hier sehen wir Methoden wie appendChildXML(), insertXmlBefore() und deleteXml(). Wichtig sind in dieser Gruppe aber auch die Funktionen extract() und extractValue(), die zur Extraktion von Daten gebraucht werden. Diese Methoden dienen der Überführung von XML-Instanzen in relationale Strukturen. Eine wesentliche weitere Funktion ist schließlich transform(), mit deren Hilfe eine XML-Instanz durch ein übergebenes XSLT in eine andere Form konvertiert werden kann. So wäre es zum Beispiel denkbar, dass XML-Informationen durch ein Stylesheet in HTML überführt und anschließend direkt in einen HTTP-Stream geschrieben und im Browser dargestellt werden. Sie werden im weiteren Verlauf noch Beispiele für diese Typmethoden sehen.

Umformung von XML mittels XSLT

Eine besonders praktische Eigenschaft des Datentyps XMLType ist seine Fähigkeit, direkt durch XSLT umgeformt zu werden. XSLT ist eine Technologie, die es erlaubt, mit XML-Mitteln XML-Instanzen in andere Ausgabeformate umzuwandeln. Prinzipiell wäre hier beinahe jedes Ausgabeformat möglich, häufig ist aber die Konvertierung in HTML und in anders aufgebautes XML. Nehmen wir ein einfaches Szenario: Eine XML-Nachricht soll für verschiedene Empfänger unterschiedlich aufgebaut werden, es handelt sich aber stets um XML-Instanzen. Dann könnten mehrere XSLT-Instanzen für die verschiedenen Empfänger erstellt werden, die in einer Tabelle vorgehalten werden. Um nun eine XML-Instanz für einen bestimmten Empfänger umzuwandeln, könnte diese Tabelle referenziert, das entsprechende XSLT ausgewählt und mit diesem die XML-Instanz in einem Schritt umgewandelt werden.

Zentral für diese Funktion ist die Member Function transform des XMLType. Diese Funktion erwartet eine XSLT-Instanz und transformiert die XMLType-Instanz. Als Ergebnis wird die umgewandelte XMLType-Instanz geliefert. Sehen wir uns diesen Vorgang einmal an einem Beispiel an.

Als Ausgangs-XML nutzen wir eine View, die unsere Mitarbeiter als XMLType-Instanz liefert:

create or replace view emp_xml_vw as
select xmlelement("Mitarbeiterliste",
              xmlagg(
               xmlelement("Mitarbeiter",
                xmlattributes(
                    empno "id",
                    hiredate "einstellDatum",
                   deptno "abteilungId"
                 ),
                xmlforest(
                initcap(ename) "Name",
                initcap(job) "Beruf",
                sal "Gehalt"
             )
           )
        order by ename
      )
    ) resultat
  from emp;


View created.

Code-Beispiel: Erzeugung der »XMLType«-Instanzen

Nun benötigen wir noch eine kleine Tabelle zur Aufnahme der XSLT-Instanzen:

create table emp_xslt(
       id number,
       xslt xmltype,
       constraint pk_emp_xslt primary key(id)
 );


Table created.

Code-Beispiel: Erzeugung einer Tabelle für die XSLT-Instanzen

Ich möchte Sie nun nicht mit meinen überbordenden XSLT-Kenntnissen blenden (obwohl ich das natürlich könnte ;-), sondern die Aufgabe so einfach wie möglich formulieren: Ein Empfänger benötigt eine Liste der Manager unseres Unternehmens als XML-Instanz. Diese Instanz soll aus der Liste der Mitarbeiter generiert werden. Dabei soll gleichzeitig das Element Mitarbeiterliste in ManagerListe umbenannt werden.

Das XSLT für diese Aufgabe sieht so aus:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <xsl:apply-templates/>
    </xsl:template>
    <xsl:template match="Mitarbeiterliste">
        <ManagerListe>
            <xsl:apply-templates select="Mitarbeiter[Beruf = 'Manager']"/>
        </ManagerListe>
    </xsl:template>
    <xsl:template match="Mitarbeiter">
        <xsl:copy-of select="."/>
    </xsl:template>
</xsl:stylesheet>

Code-Beispiel: Einfaches Stylesheet zur Umwandlung der XML-Instanz

XSLT-Instanzen definieren einen Namensraum xsl, unter dem die Befehle für die Umwandlung zusammengefasst werden. Alle XML-Elemente, die diesen Namensraum referenzieren, stellen daher Umwandlungsanweisungen dar. Alle Elemente, die diesen Namensraum nicht referenzieren, werden unverändert in den Ausgabebaum übernommen.

Sehen wir uns einige Teile an: Zunächst fällt auf, dass das Stylesheet aus drei sogenannten Templates besteht. Ein Template ist eine Bearbeitungsvorschrift für ein XML-Element. Für welches Element das Template gilt, wird über das Attribut match gesteuert. Alle Angaben zu Elementen sind XPath-Ausdrücke, die, einer Ordnerhierarchie vergleichbar, innerhalb eines XML-Dokuments navigieren können. Dabei ist es wichtig, zu wissen, wo unsere aktuelle Position im XML-Baum ist. Die »Pfadangaben« in XPath sind nämlich im Regelfall relative Angaben, die vom aktuellen Standpunkt aus ausgewertet werden. Im ersten Template befinden wir uns in der Wurzel der XML-Instanz, noch oberhalb des Wurzelelements, daher der XPath-Ausdruck / im Attribut match. Die einzige Aufgabe dieses Templates ist es, die aktuelle Pfadposition auf das Wurzelelement Mitarbeiterliste zu verschieben und die dafür definierten Templates aufzurufen.

Das Template für die Mitarbeiterliste definiert nun ein neues XML-Element Managerliste und verschiebt die aktuelle Position im Baum nacheinander auf alle Kindelemente Mitarbeiter, für die gilt, dass deren Kindelement Beruf den Wert Manager hat. Dies wird durch den Knotentest im select-Attribut der Anweisung xsl:apply-templates gesteuert.

Nun wird dreimal das Template für den Mitarbeiter aufgerufen, weil drei Manager in der XML-Instanz vorhanden sind. Alles, was dieses Template tut, ist, das Element Mitarbeiter inklusive aller Attribute und Kindelemente in den Ausgabebaum zu kopieren. Anschließend gehen wir wieder zum Template für die Mitarbeiterliste zurück, fügen das Endtag des Elements ManagerListe an und schließen die Umwandlung ab. Fertig ist folgende neue XML-Instanz:

<?xml version="1.0" encoding="UTF-8"?>
<ManagerListe>
    <Mitarbeiter id="7698" einstellDatum="1981-05-01" abteilungId="30">
        <Name>Blake</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2850</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter id="7782" einstellDatum="1981-06-09" abteilungId="10">
        <Name>Clark</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2450</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter id="7566" einstellDatum="1981-04-02" abteilungId="20">
        <Name>Jones</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2975</Gehalt>
    </Mitarbeiter>
</ManagerListe>

Code-Beispiel: Das Ergebnis der Umwandlung

Gut, das sollte das Prinzip zeigen. Nun kopieren wir uns das Stylesheet in die Tabelle emp_xslt und lassen unser Abfrageergebnis direkt in SQL in das neue Format umformen:

insert into emp_xslt
  values (10, xmltype(q'{
  <?xml version="1.0" encoding="UTF-8"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
     <xsl:template match="/">
           <xsl:apply-templates/>   
     </xsl:template>
     <xsl:template match="Mitarbeiterliste">
        <ManagerListe>
            <xsl:apply-templates select="Mitarbeiter[Beruf = 'Manager']"/>
        </ManagerListe>
     </xsl:template>
     <xsl:template match="Mitarbeiter">
            <xsl:copy-of select="."/>
     </xsl:template>
  </xsl:stylesheet>
    }'));


1 row created.


commit;


Commit complete.

Code-Beispiel: Speicherung der XSLT-Instanz in der Datenbank

Der Rest ist nun überraschend einfach:

select e.resultat.transform(x.xslt) umwandlung
     from emp_xml_vw e
   cross join emp_xslt x
   where x.id = 1;


UMWANDLUNG
--------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<ManagerListe>
    <Mitarbeiter id="7698" einstellDatum="1981-05-01" abteilungId="30">
        <Name>Blake</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2850</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter id="7782" einstellDatum="1981-06-09" abteilungId="10">
        <Name>Clark</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2450</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter id="7566" einstellDatum="1981-04-02" abteilungId="20">
        <Name>Jones</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2975</Gehalt>
    </Mitarbeiter>
</ManagerListe>

Code-Beispiel: Umwandlung von XML in SQL

Natürlich könnte man eine solche Umwandlung auch mit SQL/XML-Mitteln durchführen. Doch ist dieser Ansatz insofern sehr elegant, als von einem generischen XML-Format ausgehend nun durch einfache, empfängerbezogene XSLT-Instanzen in einer Tabelle beliebige Ausgabeformate (auch zum Beispiel CSV und ähnliche) gerechnet werden können, denn es ist ja nur erforderlich, mehrere XSLT-Instanzen unter entsprechenden IDs in der Tabelle emp_xslt zu hinterlegen. Zudem kann die Stylesheet-Erstellung an andere Mitglieder des Entwicklerteams abgegeben werden, die Kenntnis von SQL/XML ist dort nicht erforderlich, ebenso wie die Datenbankentwickler nicht notwendigerweise XSLT erlernen müssen.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Oracle & XML bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © Rheinwerk Verlag, Bonn 2014, 2. Auflage
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Oracle PL/SQL: Das umfassende Handbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, www.rheinwerk-verlag.de, service(at)rheinwerk-verlag.de