XML mit PL/SQL verarbeiten

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

Nach einem Überblick über die verschiedenen Möglichkeiten, XML zu erzeugen, soll es nun darum gehen, mit existierenden XML-Daten zu arbeiten. Auch hier gibt es verschiedene Möglichkeiten:

  • XQuery
  • XSLT
  • händische Programmierung mit dem DOM-Baum in PL/SQL

Die beiden ersten Wege haben wir uns grob bereits angesehen. In der Literatur und auch in einigen Projekten sehe ich allerdings eine Vorliebe für Interaktionen mit dem DOM-Baum, wohl weil dies in der Java-Welt üblicher ist oder mit PL/SQL-Bordmitteln erledigt werden kann. Das ist in der Datenbank normalerweise nicht nötig und aus meiner Sicht recht kompliziert. Zum einen steht mit der updateXML-Anweisung in SQL/XML ein Weg offen, gezielt (und ohne die gesamte XML-Instanz neu zu erstellen) Teilbäume bis hinunter zu einem einzelnen Attributwert zu erzeugen, zu löschen oder hinzuzufügen (ab Version 12c: XQuery). Zum anderen sind viele, insbesondere komplexere Änderungen deutlich einfacher durch XSLT zu erreichen. Oft höre ich die Begründung, dass man sich mit einer DOM-Manipulation wohler fühle, zumal man XQuery oder XSLT ja erst einmal lernen müsste. Haben Sie häufig mit solchen Umwandlungen zu tun, ist dies jedoch der mit Abstand eleganteste Weg für solche Änderungen.

Die Programmierung mittels DOM-Baum

Bei der Verarbeitung von XML sind einige Grundlagen wichtig. Zum einen ist die Kenntnis des DOM-Programmierungsmodells für die Programmierung in PL/SQL unerlässlich, zudem erschweren Namensräume die Arbeit mit diesen Datenstrukturen. Dieser Abschnitt erläutert diese Begriffe und stellt außerdem wichtige Werkzeuge von Oracle vor, mit denen die Programmierung von XML durchgeführt werden kann.

Exkurs: Der DOM-Baum und der SAX-Ansatz
Die Grundlage einer Programmierung von XML-Instanzen ist der DOM-Baum. Diese Spezifikation legt genau fest, auf welche Weise eine XML-Instanz im Arbeitsspeicher abgebildet werden muss, um durch die entsprechenden Parser verarbeitet werden zu können. Sollte ich den Eindruck erweckt haben, der DOM-Baum sei eine der Wurzeln allen Übels in Hinblick auf langsame XML-Verarbeitung, so stimmt dies nur zum Teil: Tatsächlich verbraucht diese Darstellungsform sehr viel Arbeitsspeicher, denn jedes »Fitzelchen« der XML-Instanz (sogar der sogenannte Weißraum, also Leerzeichen, Tabulatoren und Absatzzeichen, die der Formatierung dienen) stellt einen eigenen Knoten in der DOM-Repräsentation dar. Andererseits ist diese Zugriffsmethode die optimale Art der Repräsentation, wenn Sie wahlfreien und häufig über das gesamte Dokument verteilten Zugriff auf die einzelnen Knoten benötigen. Dies ist meiner Erfahrung nach gerade in nachrichtenbasierten Systemen jedoch eher selten der Fall. Die XML-Gemeinde hat daher schon sehr früh nach einer Alternative Ausschau gehalten. Herausgekommen ist die Simple API for XML (SAX), die im Gegensatz zum DOM-Baum lediglich einmal seriell durch die XML-Datei liest und dabei jedes Vorkommnis (neues Element gefunden, Elementattribut gefunden etc.) als Ereignis an die aufrufende Umgebung propagiert. Dort können dann Ereignis-Handler diese Ereignisse auffangen (einer Triggerprogrammierung nicht unähnlich) und die Ergebnisse direkt verarbeiten. Der Vorteil: Dadurch muss keine Speicherrepräsentation im Arbeitsspeicher aufgebaut werden, und dadurch werden Ressourcen geschont. Der Nachteil: Ein wahlfreier Zugriff auf die einzelnen Elemente der XML-Instanz ist nur möglich, indem die Datei immer und immer wieder neu gelesen wird. Solche Szenarien sind dann im Regelfall deutlich aufwendiger als mit DOM. Und schlimmer: Dieser Ansatz ist in PL/SQL schlicht nicht möglich, weil PL/SQL keine Ereignisse kennt (nun ja, außer den Triggern, die allerdings eher von der SQL-Seite aus ausgelöst werden. Und auch diese Ereignisse lassen sich in PL/SQL nicht erzeugen, sondern lediglich verwenden).

Wenn Sie mit dem DOM-Baum arbeiten möchten, tun Sie dies vor allem mit den für PL/SQL mitgelieferten Packages. Diese Packages werden wir im nächsten Abschnitt näher beleuchten. Die Navigation im DOM-Baum erfolgt entweder über XPath oder aber, indem Sie sich mit Methoden durch den Baum »hangeln«, ähnlich wie Sie auch durch ein Dateisystem navigieren, indem Sie zum Kindelement eines Knotens gehen oder auch zum Elternelement. Gerade die Navigation im XML-Baum ist allerdings eine fehlerträchtige Angelegenheit, sodass Sie wahrscheinlich hauptsächlich mit diesen Problemen zu kämpfen haben werden.

Exkurs: Namensräume
Eine weitere Besonderheit von XML ist seine Fähigkeit, Elemente an einen Namensraum zu binden. Gerade für Einsteiger in XML (und besonders dann, wenn Sie solche Konzepte nicht bereits aus Java oder ähnlichen Sprachen kennen) verursachen Namensräume eine erhebliche Lernkurve. Vergleichen Sie einen Namensraum in etwa mit einem Schema in der Oracle-Datenbank. Ist eine Tabelle im Schema A registriert, kann ein Administrator diese Tabelle zwar sehen, doch muss er den Schemanamen A voranstellen. Ähnlich funktioniert dies auch in XML: Wird ein Element referenziert, muss ihm der Namensraum vorangestellt werden. Fehlt ein Namensraum, wird normalerweise der Default-Namensraum verwendet. In allen anderen Fällen muss jede Suche, jede Navigation im entsprechenden Namensraum des XML-Elements durchgeführt werden. Namensraumbezogene Arbeiten sind in PL/SQL jederzeit möglich, allerdings für viele Neueinsteiger zusätzlich verwirrend.

Der Oracle-Parser

Zur Verarbeitung von XML innerhalb von PL/SQL stehen Packages zur Verfügung. Diese Packages bieten eine Schnittstelle zum Oracle-Parser für XML an, mit deren Hilfe einige der wichtigsten Funktionen des Parsers genutzt werden können. Leider ist, wie bereits besprochen, in PL/SQL die Nutzung der SAX-Schnittstelle des Oracle-Parsers nicht möglich, da PL/SQL keine Ereignisse kennt und daher mit diesem Ansatz nichts anfangen kann. Oracle stellt den XML-Parser jedoch auch als separaten Download bereit, sodass diese Funktionalität zum Beispiel aus Java genutzt werden kann. Dieser Parser ist in der Lage, XML zu parsen, zu validieren (gegen DTD und XSD) und mittels XSLT umzuformen. Eine Umformung mittels XSL-FO ist nicht möglich (wie bei den allermeisten anderen Parsern auch), sodass für diese Aufgabe zusätzliche Parser (zum Beispiel Apache FOP oder der Renderer von AntennaHouse) oder aber externe Werkzeuge wie der BI-Publisher von Oracle eingebunden werden müssen. Die Grundlage für die Programmierung ist also immer der Oracle XML Parser, auch dann, wenn er über eine Programmierschnittstelle aus PL/SQL-Packages genutzt wird.

Die XML-Packages

Gerade der Bereich der XML-Verarbeitung ist von Datenbankversion zu Datenbankversion erheblichen Änderungen unterworfen. So hat die Version 9.2 erstmalig ein stabiles Set von XML-Packages zur Verfügung gestellt, die in Version 10g deutlich erweitert und in Version 11g noch einmal deutlich verändert wurden. Die Tendenz besteht darin, XML-Verarbeitung zunehmend nativ in den Datenbankkern zu integrieren und dabei dem Programmierer die erheblichen Performance-Vorteile einer Programmierung in C gegenüber der ursprünglichen Implementierung in Java zur Verfügung zu stellen. Gleichzeitig ist der Umfang der Implementierung stark gewachsen. Aus diesen Gründen muss ich mich bei der Darstellung der wichtigsten PL/SQL-Packages auf eine Version konzentrieren, und das wird Version 11g sein. Hier gilt, stärker noch als in anderen Bereichen, dass Sie in jedem Fall die Dokumentation Ihrer Datenbankversion zurate ziehen sollten, wenn Sie die Programmierung von XML angehen. Glücklicherweise stehen Ihnen sehr detaillierte Informationen zur Verfügung, sodass Sie sich auf der Grundlage dieser Übersicht sicher schnell einen Überblick verschaffen können. Informationen finden Sie einerseits in dem Dokument zur XML-Entwicklung, dem XML DB Developer’s Guide, das sich mit der PL/SQL-Schnittstelle zum Oracle-Parser sowie der Entwicklung gegen die XML Database (XDB) beschäftigt, die wir unter Die XML-Datenbank näher betrachten werden. Zusätzliche Informationen finden Sie zudem in der PL/SQL Packages and Types Reference, die ja Dokumentationen zu allen Packages enthält, so natürlich auch zu den XML-Packages. Die meisten relevanten Packages führen XML in ihrem Namen, sodass die Zuordnung keine Schwierigkeiten bereitet. Sehen wir uns also einmal diese Packages an.

»dbms_xmlparser«, »dbms_xslprocessor« und »dbms_xmldom«

Diese drei Packages stellen mit ihren Methoden das Herzstück der XML-Integration dar, denn hier verbirgt sich die Schnittstelle zum Oracle XML Parser sowie zum XML-Dokument. Diese Packages orientieren sich am Programmiermodell des W3C für DOM-Bäume, und – dies sei vorweg gesagt – diese Vorgabe ist sehr umfassend, aber nicht immer intuitiv. Die folgenden Ausführungen sind daher hauptsächlich für diejenigen von Interesse, die die Interna der DOM-Programmierung für ein Projekt benötigen, und sind dennoch lediglich als Überblick und Einstieg gedacht. Für die praktische Arbeit kann ich die Verwendung von XML mithilfe dieser Packages nicht recht empfehlen, denn sie erfordert eine längere Einarbeitungszeit in die Denkweise von DOM-Strukturen und in die Packages, die (wie im Falle des Packages dbms_xmldom) aus sehr vielen Methoden mit zum Teil überlagerter Funktionalität bestehen und sich auf eher theoretische Weise den maximalen Möglichkeiten der Programmierung von XML widmen. Im Übrigen sehen Sie hier auch ein im Vergleich zu PL/SQL anderes Programmierparadigma, das eher an objektorientierte Programmierung in Java oder C# erinnert. Es hat viele Parameter, die gesetzt werden müssen, und viele, viele Einzelfunktionen, die nacheinander aufgerufen werden müssen, um letztlich triviale Aufgaben erledigt zu bekommen.

dbms_xmlparser wird genutzt, um XML-Dokumente einzulesen und in einen DOM-Baum zu überführen. Dieses Package steuert auch die Validierung der Dokumente. Nachdem ein Dokument geparst wurde, kann es mit dem Package dbms_xmldom in allen Einzelheiten untersucht und geändert oder durch das Package dbms_xslprocessor umgeformt werden. Das Package dbms_xmldom ist sicher eines der komplexesten Packages, die Oracle mitliefert, allein schon von der Zahl der Methoden her, die sich in ihm verbergen. Schon aus diesem Grund verbietet sich eine komplette Vorstellung der Methoden in diesem Kontext. Es soll uns reichen, die grundsätzliche Arbeitsweise zu verstehen. Weiterführende Informationen müssen Sie sich dann aus den oben genannten Dokumenten oder einem weiterführenden Buch beschaffen.

Sehen wir uns die grundsätzliche Arbeitsweise an einem Beispiel an. Wir möchten zunächst eine Zeichenkette mit Hilfe von dbms_xmlparser in einen DOM-Baum überführen, bevor wir diesen in einem zweiten Beispiel durch das Package dbms_xmldom erweitern:

set serveroutput on
declare
     l_char_doc varchar2(2000);    
     l_dom_doc dbms_xmldom.domDocument;
     l_dom_node dbms_xmldom.domNode;
     l_parser dbms_xmlparser.parser;
     l_buffer varchar2(2000);
   begin
     l_char_doc :=
     '<Mitarbeiter><Name>King</Name></Mitarbeiter>';
     -- l_parser instanziieren und vorbereiten
     l_parser := dbms_xmlparser.newParser;
     -- Zeichenkette parsen und DOM erzeugen
     dbms_xmlparser.parseBuffer(l_parser, l_char_doc);
     l_dom_doc := dbms_xmlparser.getDocument(l_parser);
     -- Einzelnes Element aus dem Dokument lesen
     l_dom_node:= dbms_xmldom.makeNode(l_dom_doc);
     -- Element ausgeben
     dbms_xmldom.writeToBuffer(l_dom_node, l_buffer);
     dbms_output.put_line(l_buffer);
     -- Aufräumen
     dbms_xmldom.freeDocument(l_dom_doc);
     dbms_xmlparser.freeParser(l_parser);
    end;
/
<Mitarbeiter>
    <Name>King</Name>
</Mitarbeiter>


PL/SQL-Prozedur erfolgreich abgeschlossen.

Code-Beispiel: Erstellung einer XML-Instanz mittels »dbms_xmldom«

Wie Sie am Ausgabeergebnis sehen können, ist unsere ursprüngliche Zeichenkette nun eine XML-Datei geworden, die entsprechend formatiert werden konnte. Natürlich ist dieser Weg sehr umständlich, verglichen mit dem Aufruf einer Konstruktormethode von XMLType. Allerdings haben wir hier eine sehr viel feinere Kontrolle, zum Beispiel über die Validierung der Datei beim Einlesen, die Ausgabe von Warnmeldungen oder die Behandlung von Weißraum. Ungewöhnlich erscheint, dass wir zunächst in Zeile 12 einen neuen Parser erstellen müssen, bevor wir ihn verwenden können. Das ist typisch für die Objektorientierung und entspricht letztlich dem Initialisieren eines temporären LOBs oder eines Kollektionstyps. Anschließend übergeben wir diesem neuen Parser die Zeichenkette zum Parsen (Zeile 14). Möchten Sie das Verhalten des Parse-Vorgangs steuern, müssen Sie entsprechende Einstellungen vor dem Aufruf dieser Prozedur vornehmen. Das Ergebnis ist nun eine geparste XML-Instanz innerhalb des Parsers. Mit dem folgenden Aufruf der Funktion dbms_xmlparser.getDocument() in Zeile 15 kopieren wir dieses interne XML-Dokument in die Variable dom_doc zur weiteren Bearbeitung. Die Variable dom_doc ist vom Typ dbms_xmldom.domDocument und stellt das gesamte Dokument dar. Inhaltlich (in XML-Termini) handelt es sich dabei um die Wurzel (Root) des Dokuments inklusive des gesamten Inhalts. Technisch gesehen, ist der Dokumentknoten eine Spezialisierung des allgemeinen Knotens vom Typ domNode. Da die Prozedur writeToBuffer einen solchen allgemeineren domNode als Eingabeparameter benötigt, müssen wir zunächst den Wurzelknoten in den allgemeineren Knoten überführen. Dies geschieht in Zeile 17. Zeile 19 schließlich gibt diesen domNode als Zeichenkette aus. Wie gesagt: viele kleine Trippelschritte ...

Wichtig ist die Freigabe der Ressourcen zum Ende der Verarbeitung durch die entsprechenden Methoden des Packages. Dies gilt insbesondere (wieder einmal), wenn der Parser in einem Package deklariert ist und für die Dauer einer ganzen Session bestehen könnte. Bitte beachten Sie, dass nach dem Parsen des Dokuments das Package dbms_xmldom die Kontrolle übernimmt und die eigentliche Arbeit am XML-Dokument durchführt. Diese Arbeitsweise werden wir im folgenden Beispiel noch erweitern, indem wir der XML-Instanz noch einen weiteren Knoten hinzufügen. Um den Code zu kürzen (er ist immer noch lang genug!), verwende ich anstelle von dbms_xmlparser nun allerdings den Datentyp XMLType mit seinem Konstruktor, um die XML-Instanz zu erzeugen:

declare
     l_xml_doc xmlType;    
     l_dom_doc dbms_xmldom.domDocument;
     l_root dbms_xmldom.domNode;
     l_root_node dbms_xmldom.domNode;
     l_out_buffer varchar2(2000);

     procedure setXmlHeader(
      p_version_string in varchar2,
     p_encoding in varchar2)
   as
   begin
     dbms_xmldom.setVersion(l_dom_doc, p_version_string);
     dbms_xmldom.setCharset(l_dom_doc, p_encoding);
   end setXmlHeader;

   function getChildNode(
     p_element_name in varchar2,
     p_position in naturaln)
     return dbms_xmldom.domNode
     as
     nodelist dbms_xmldom.domNodeList;
   begin
     nodelist := dbms_xmldom.getElementsByTagName(
                       l_dom_doc, p_element_name);
     return dbms_xmldom.item(nodelist, p_position);
   end getChildNode;

   procedure appendChildUnder(
     p_parent_node in out dbms_xmldom.domNode,
     p_child_name in varchar2,
     p_child_value in varchar2)
   as
     l_element dbms_xmldom.domElement;
     l_node dbms_xmldom.domNode;
     l_text dbms_xmldom.domText;
   begin
     l_element := dbms_xmldom.createElement(
                    l_dom_doc, p_child_name);
     l_node := dbms_xmldom.makeNode(l_element);
     l_text := dbms_xmldom.createTextNode(
           l_dom_doc, p_child_value);
     p_parent_node := dbms_xmldom.appendChild(
                    p_parent_node, l_node);
     p_parent_node := dbms_xmldom.appendChild(
                    l_node, dbms_xmldom.makeNode(l_text));
    end appendChildUnder;

  begin
    -- Erzeuge XML und daraus den DOM-Baum
     l_xml_doc :=
      xmltype('<Mitarbeiter><Name>King</Name></Mitarbeiter>');
     l_dom_doc := dbms_xmldom.newDomDocument(l_xml_doc);
     l_root := dbms_xmldom.makeNode(l_dom_doc);
     l_root_node := getChildNode('Mitarbeiter', 0);

     -- Bearbeite den DOM-Baum
     setXmlHeader('1.0', 'ISO-8859-1');

     -- Erzeuge neues Element
     appendChildUnder(l_root_node, 'Beruf', 'President');

     dbms_xmldom.writetobuffer(l_root, l_out_buffer);
     dbms_output.put_line(l_out_buffer);
     dbms_xmldom.freedocument(l_dom_doc);
    end;
  / 
<?xml version="1.0"?>
<Mitarbeiter>
   <Name>King</Name>
   <Beruf>President</Beruf>
</Mitarbeiter>


PL/SQL-Prozedur erfolgreich abgeschlossen.

Code-Beispiel: W3C-konforme Programmierung in PL/SQL

Ganz schön einfach, nicht wahr? Allerdings muss man daran erinnern, dass der Großteil dieser überbordenden Komplexität vom Standard DOM vorgegeben ist und nicht von PL/SQL. In Java ebenso wie in anderen Programmiersprachen haben sich vereinfachte Schnittstellen entwickelt, die diese Programmierweise hinter Hilfsfunktionen verbergen. Eine solche einfachere Schnittstelle wird in PL/SQL allerdings nicht mitgeliefert, sondern zum Teil von SQL/XML angeboten. Möchten Sie häufig auf diesem Niveau mit dem DOM-Baum arbeiten, bietet sich zumindest ein eigenes Package mit sinnvoll gekapselten Hilfsmethoden für diese Art Arbeit an. Lassen Sie mich aber doch noch erklären, was überhaupt passiert, damit Sie das Beispiel wenigstens nachvollziehen können.

Lesen Sie den Code von unten nach oben. Starten wir in Zeile 49, dem eigentlichen Hauptprogramm. Ich erzeuge mit der Konstruktormethode zunächst eine XML-Instanz, die ich anschließend erweitern möchte. Aus dieser Instanz erzeuge ich nacheinander den DOM-Baum (Zeile 53), den Wurzelknoten (Zeile 54, verwechseln Sie diesen bitte nicht mit dem Wurzelelement!) und das Wurzelelement (Zeile 55). Lassen Sie uns noch nicht in die Hilfsmethoden schauen, sondern zunächst einmal einen Überblick gewinnen.

Anschließend setze ich einige weitere Informationen, zum Beispiel die Version und die Zeichensatzkodierung, und erzeuge danach ein neues Kindelement unter dem Wurzelelement. Dieses Wurzelelement heißt in unserem Beispiel Mitarbeiter und hat derzeit nur ein Kind, nämlich Name. Nun jedoch soll ein neues Element Beruf mit dem Wert President erzeugt werden. In der DOM-Diktion sind jedoch sowohl das Kindelement als auch der darin enthaltene Elementwert eigene Knoten, sodass wir beide separat erzeugen müssen. Das ist mir zu umständlich, daher nutze ich auch hier eine Hilfsfunktion. Der Rest des Codes liest das Ergebnis und gibt es aus. Bevor wir zu den Hilfsfunktionen kommen, mag Sie noch ein Punkt irritieren: Wir fügen ein Kindelement an das Wurzelelement an, das wir in einer eigenen Variablen zur Verfügung haben: root_node. Die Ausgabe erfolgt jedoch über die Variable root, also das Gesamtdokument. Woher weiß root, dass wir ein neues Kindelement in root_node eingefügt haben? Müssten wir nicht auf irgendeine Weise in root den DOM-Baum aktualisieren? Nein, das ist nicht erforderlich, denn die verschiedenen Variablen zeigen schlicht auf verschiedene Bereiche eines DOM-Baums. Ändern Sie innerhalb eines Zeigers Teile des Baums, ist diese Änderung direkt für alle anderen Variablen sichtbar, da diese auf denselben DOM-Baum zeigen.

In den Hilfsfunktionen wird die eigentliche Arbeit gemacht. Einfach ist die Prozedur setXmlHeader() in Zeile 8–15, das erklärt sich von selbst und muss nicht besprochen werden. Die Funktion getChildNode() in Zeile 17–27, die mir ein Kindelement zu einem gegebenen Vaterknoten liefert, ist da schon von anderem Kaliber: Zunächst müssen wir berücksichtigen, dass (allgemein gesprochen, nicht in diesem konkreten Fall) mehrere Kinder gleichen Namens unter einem Element vorkommen können. Daher liefert die Funktion dbms_xmldom.getElementsByTagName() eine Knotenliste zurück. Die Suche erfolgt bei dieser Funktion über den Namen des Kindelements, den ich als Parameter der Hilfsfunktion übergebe. Nun entnehme ich dieser Knotenliste das n-te Element (das ebenfalls als Parameter übergeben wird) und liefere es zurück. Die Knotenliste ist, im Gegensatz zu SQL, 0-basiert, daher muss 0 für das erste Kindelement übergeben werden.

Als Letztes folgt die Hilfsmethode zum Einfügen eines Kindelements unter dem angegebenen Vaterelement appendChildUnder() in den Zeilen 28–47. Was wir hier tun müssen, ist natürlich äußerst unschön. Diese Funktionalität einmal in einer sinnvollen Hilfsmethode zu verbergen ist eigentlich Pflicht. Wahrscheinlich sollten Sie auch eine andere Aufteilung der Hilfsmethoden für sich finden, als ich es hier getan habe, mir ging es nur um möglichst einfach zu verstehenden Code, nicht um Wiederverwendbarkeit. Das Prinzip: Wir erstellen zunächst zwei Elemente: eins für das eigentliche Kindelement und eins für den Elementwert. Enthält ein Element Text, wird dieser in einem eigenen Textknoten gespeichert. Dieser Textknoten wird zunächst unabhängig vom Elementknoten erzeugt und später in diesen eingefügt. DOM unterscheidet Elementknoten und Textknoten als Spezialisierung des allgemeineren Knotens, sodass wir diese Elemente zunächst noch in Knoten umwandeln müssen, bevor sie durch die Funktion appendChild(), die einen Knoten erwartet, eingefügt werden können. Dann geht das Ganze so: Wir fügen einen Kindknoten unter dem Vaterknoten ein, und zwar im ersten Schritt den neuen Elementknoten unter dem übergebenen Vaterelementknoten. Anschließend fügen wir unter den neu eingefügten Elementknoten noch den Elementwert als Textknoten ein. Damit hat der Vaterknoten ein neues Kind mit einem Elementwert. Und am Ende haben wir dann: ein Kindelement eingefügt! Wie gesagt: Lasten Sie dies nicht PL/SQL an, das ist die durch das W3C vorgegebene Programmierweise für DOM-Bäume, zu der PL/SQL nach Möglichkeit kompatibel bleiben möchte.

Die Arbeitsweise des XSLT-Prozessors geht ähnlich vor, ich erspare mir hier allerdings ein Beispiel, da Sie sich denken können, wie das im Grunde aussieht: Zunächst parsen Sie ein XML-Dokument sowie ein XSLT-Dokument mit einem Parser. Anschließend instanziieren Sie einen XSLT-Prozessor und übergeben diesem, nachdem Sie die Details der gewünschten Art der Umwandlung eingestellt haben, beide Instanzen und lassen das Ergebnis konvertieren. Auch hier ist die Vorarbeit deutlich länger als der eigentliche Transformationsprozess, der dann irgendwann in einer Zeile abgehandelt werden kann. Doch bis es erst einmal so weit ist, vergehen doch viele Zeilen Code.

»dbms_xmlgen«, »dbms_xmlquery«, »dbms_xmlstore« und »dbms_xmlsave«

Diese vier Packages beschäftigen sich mit der Erzeugung, Aktualisierung, Speicherung und Löschung von XML-Daten in Datenbanktabellen oder aus diesen heraus. Da wir unter Das Package »dbms_xmlgen« bzw. »sys_xmlgen« bereits die Erzeugung von XML aus relationalen Daten mittels PL/SQL besprochen haben, brauchen wir dies hier nicht noch einmal zu tun. Dieses Package hat einen vergleichbaren Funktionsumfang wie das Package dbms_xmlquery. Der einzige (aber wesentliche) Unterschied ist die Implementierung von dbms_xml gen in C und im Datenbank-Kernel. Daher ist dieses Package dbms_xmlquery vorzuziehen, falls keine signifikanten Gründe dagegensprechen. dbms_xmlquery ist Teil des XML SQL Utilitys (XSU), einer in Java implementierten Sammlung von XML-Utilitys. Die beiden Packages dbms_xm lsave und dbms_xm lstore werde ich ebenfalls nicht einzeln besprechen, da sie funktional weitestgehend deckungsgleich sind. Im Gegensatz zu dbms_xmlstore ist dbms_xmlsave ebenfalls in Java (als Teil der XSU) und nicht im Datenbank-Kernel kodiert. Die Empfehlung lautet also: Verwenden Sie dbms_xmlgen und dbms_xmlstore, wann immer es möglich ist.

dbms_xmlstore besteht aus relativ wenigen, weitgehend selbsterklärenden Methoden wie deletexml(), insertxml() und updatexml(). Die Arbeitsweise dieses Packages ist auch relativ einfach und schnell erklärt: Wieder existiert ein Kontext, in dessen Umfeld Tabelleneinstellungen und Parameter gelten. Die oben beschriebenen Funktionen liefern stets die Anzahl der bearbeiteten Zeilen als number zurück, sodass auch diese Aufgabe effizient gelöst ist. Lediglich die Einstellungen müssen erläutert werden, denn die Denkweise ist etwas ungewöhnlich. Sehen wir uns zunächst ein Beispiel für die Aktualisierung einer Tabelle mithilfe dieses Packages an. Im Szenario werden XML-Daten in einer Form, die dem Ergebnis eines Aufrufs von dbms_xmlgen auf die Zieltabelle entspricht, genutzt, um die Tabelle wiederum zu aktualisieren. Wir erinnern uns: Hätten wir die Anweisung select * from emp durch dbms_xmlgen in XML umwandeln lassen, wäre die XML-Instanz durch ein Element ROWSET und jede Zeile durch ein Element ROW umschlossen gewesen. Die Elementnamen hätten den Spaltennamen in Versalien entsprochen. Gehen wir von diesem Standardformat aus.

Um zu erkennen, welche Zeile genau aktualisiert werden soll, benötigen wir eine where-Klausel. In diesem Package wird diese Klausel aus einem Element der XML-Instanz gefüllt. Wir können dem Package diese Schlüsselspalte mitteilen. Außerdem können wir festlegen, welche Spalten tatsächlich aktualisiert werden sollen. Tun wir dies nicht, werden alle Spalten aktualisiert (eventuell auf den gleichen Wert), was Probleme nach sich ziehen kann, wenn Sie zum Beispiel Trigger auf diese Tabelle gelegt haben. Diese Trigger lösen eventuell auch dann aus, wenn die überwachten Spalten gar nicht geändert wurden. Es ist also (nicht nur aus Gründen der Performance für dieses Package selbst, da keine Analyse der Spalten erforderlich ist) eine Best Practice, die zu aktualisierenden Spalten explizit anzugeben. Dies tun wir dann auch im folgenden Beispiel:

select ename, job, sal
  fromemp
  where empno = 7369;


ENAME      JOB        SAL
-------- --------- ----------
SMITH      CLERK      800



declare
      l_ctx dbms_xmlstore.ctxType;     
      l_rows_processed number;
      l_xml_doc clob :=
         '<ROWSET>
            <ROW>
               <EMPNO>7369</EMPNO>
               <ENAME>SCHMITZ</ENAME>
                    <SAL>1200</SAL>
                  <JOB>ANALYST</JOB>    
            </ROW>
          </ROWSET>';
   begin
     -- Der Kontext ist die zu aktualisierende Tabelle
     l_ctx := dbms_xmlstore.newContext('SCOTT.EMP');
     -- Lösche eventuelle Voreinstellungen
     dbms_xmlstore.clearKeyColumnList(l_ctx);
     dbms_xmlstore.clearUpdateColumnList(l_ctx);
     -- Schlüsselspalte ist EMPNO
     dbms_xmlstore.setKeyColumn(l_ctx, 'EMPNO');
     -- Aktualisiert wird _nicht_ SAL
     dbms_xmlstore.setUpdateColumn(l_ctx, 'ENAME');
     dbms_xmlstore.setUpdateColumn(l_ctx, 'JOB');
     -- Aktualisierung durchführen
     l_rows_processed := dbms_xmlstore.updateXML(l_ctx, l_xml_doc);
     -- Aufräumen
     dbms_xmlstore.closeContext(l_ctx);
   end;
 /


PL/SQL-Prozedur erfolgreich abgeschlossen.



select ename, job, sal
  fromemp
  where empno = 7369;


ENAME      JOB        SAL
-------- --------- ----------
SCHMITZ    ANALYST    800

Code-Beispiel: Aktualisierung von XML-Daten mittels »dbms_xmlstore«

Wie Sie sehen, ist die Festlegung der einzelnen Optionen und Parameter nicht sehr kompliziert. Die XML-Instanz, die für die Aktualisierung der Daten eingesetzt wird, enthält alle zu ändernden Daten. Durch die Festlegung der Schlüsselspalte und der zu ändernden Spalten ist zudem ein feingranularer Zugriff auf die Tabelle möglich. Diese Anweisung kann zum Beispiel dazu genutzt werden, eine Aktualisierung von Daten auf der Oberfläche auf generische Art in die Datenbank zurückzuschreiben: Eine Prozedur kann Daten, die durch dbms_xmlgen erzeugt und durch die Oberfläche geändert wurden, entgegennehmen und in die Datenbank zurückschreiben. Diese Prozedur kann damit sowohl mit einer als auch mit vielen Zeilen umgehen und durch die interne Festlegung gleichzeitig auch steuern, welche Spalten tatsächlich geändert werden und welche nicht, und somit zum Beispiel das ungewollte Auslösen eines Triggers auf die Schlüsselspalte der Tabelle verhindern.

Analog arbeiten auch die Funktionen insertXML() und deleteXML(), sodass ich mir für diese ein Beispiel sparen kann. Die Variable rows_processed wird anschließend zudem die Anzahl der tatsächlich geänderten Zeilen enthalten.

Sonstige XML-Packages

Zusätzlich zu den bereits besprochenen Packages stehen noch weitere für spezielle Aufgaben in Bezug auf XML zur Verfügung. Diese Packages werden hier nicht im Detail besprochen, sondern lediglich mit ihrer Funktionalität erwähnt, damit Sie einen Überblick darüber erhalten, welche Packages eine nähere Untersuchung für eine gegebene Aufgabenstellung lohnen.

  1. »dbms_xmlschema«
    XML-Instanzen sind oftmals strukturierte Dateien, die gegen eine XSD validieren können. Damit die Datenbank in der Lage ist, solche Validierungen ebenfalls durchführen zu können, muss eine XSD in der Datenbank bekannt gemacht worden sein. Die Arbeit mit einer XSD beschränkt sich aber nicht allein darauf, einkommende XML-Daten zu validieren, sondern eine XSD kann auch umgekehrt aus objektrelationalen Tabellen abgeleitet werden, um zum Beispiel ein Nachrichtenformat mit XML-Mitteln zu beschreiben. Außerdem können Schemata im Laufe der Zeit geändert werden, was zu Problemen führt, wenn ältere Daten ebenfalls in der gleichen Tabelle gehalten werden sollen wie neuere, die sich auf ein bereits geändertes Schema beziehen. Solche und vergleichbare Aufgaben werden durch dbms_xmlschema adressiert. Besonders interessant ist die Möglichkeit, ein Schema in der Datenbank bekannt zu machen. Diese Funktion benötigen wir, wenn wir Daten aus einer externen Quelle gegen ein Schema innerhalb der Datenbank validieren wollen oder wenn wir eine Tabelle erzeugen möchten, die lediglich Daten eines konkreten Schemas aufnehmen können soll.
  2. »dbms_xmlindex«
    Dieses Package gibt Ihnen die Kontrolle über die Interna der Indizierung von XML-Instanzen. Grundsätzlich kann jede XML-Instanz durch einen speziellen XMLIndex indiziert werden. Dieser Indextyp wird anschließend bei jeder DML-Operation aktualisiert. Da es sich allerdings bei XML-Daten um sehr große Datenmengen handelt, können diese Operationen sehr lange dauern und die Performance nachteilig beeinflussen. dbms_xmlindex erlaubt Ihnen nun, die Pflege der XMLIndizes asynchron in verkehrsärmere Zeiten zu verlegen.
  3. »dbms_xmltranslations«
    Dieses Package erlaubt Ihnen die Arbeit mit XLIFF-Dateien (XLIFF = XML Localization Interchange File Format), einem Oasis-Standard zur Internationalisierung von Anwendungen und Nachrichten mithilfe von XML. XLIFF-Dateien enthalten die Informationen, die benötigt werden, um Anwendungen in mehreren Sprachen zu betreiben. Dabei können mit diesem Package XLIFF-Dateien extrahiert, angepasst und angewandt werden. Dieses Package bezieht sich vor allem auf XML-Dokumente, die in mehreren Sprachen innerhalb der Datenbank gespeichert werden. Beachten Sie bitte, dass dieses Package ab Version 12c als deprecated eingestuft wird.

  

<< 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