Bearbeitung von XML-Instanzen in SQL

(Auszug aus "Oracle SQL: Das umfassende Handbuch", Kapitel 17 "XML-Abfragen", von Jürgen Sieben)

Es ist ebenso möglich, bereits bestehende XML-Instanzen zu aktualisieren. Damit ist nicht gemeint, dass die gesamte XML-Instanz einer Tabellenzelle durch eine neue Fassung ersetzt würde, sondern die feingranulare Änderung von Attribut- oder Elementwerten, das Anfügen von Kindelementen oder das Löschen von Teilbäumen des XML-Dokuments. Möglich wird dies durch eine Reihe von Zeilenfunktionen, die in normalen update-Anweisungen verwendet werden können und in der XML-Instanz mittels XPath navigieren.

Um Ihnen die Funktionen dieses Bereichs im Einsatz zu zeigen, werde ich die Mitarbeiterlisten, die wir uns unter Ein einfaches Beispiel erzeugt haben, in einer Tabelle mit dem Namen dept_emp_xml speichern. Sie kennen mittlerweile genug SQL, damit ich auf ein Abdrucken der hierfür erforderlichen ctas-Anweisung verzichten kann. Im Skript zum Buch ist er natürlich aufgeführt. Die Tabelle enthält nun drei Zeilen mit den entsprechenden XML-Instanzen. Hier folgt ein Ausschnitt der ersten Zeile dieser Tabelle:

select *
 from dept_emp_xml
 where deptno = 10;


   DEPTNO       LISTE
---------- -----------------------------------------------------
            
      10        <Mitarbeiterliste abteilung="10">
                    <Mitarbeiter einstellDatum="1981-06-09" id="7782">
                        <Name>Clark</Name>
                        <Beruf>Manager</Beruf>
                        <Gehalt>2.450,00€</Gehalt>
                    </Mitarbeiter>
                    <Mitarbeiter einstellDatum="1982-01-23" id="7934">
                        <Name>Miller</Name>
                        <Beruf>Clerk</Beruf>
                        <Gehalt>1.300,00€</Gehalt>
                    </Mitarbeiter>
                    <Mitarbeiter einstellDatum="1981-11-17" id="7839">
                        <Name>King</Name>
                        <Beruf>President</Beruf>
                        <Gehalt>5.000,00€</Gehalt>
                    </Mitarbeiter>
                </Mitarbeiterliste>

Code-Beispiel: Eine Mitarbeiterliste in der Tabelle dept_emp_xml

Wie Sie sehen, habe ich mich entschlossen, anstatt RESULTAT die etwas besser passende LISTE als Spaltenbezeichner zu verwenden.

Grundlagen zu Zeilenfunktionen, die XML bearbeiten

Die Zeilenfunktionen, die ich Ihnen nun vorstellen möchte, haben gewisse Gemeinsamkeiten. Die sollten wir vorab besprechen. Zunächst einmal werden die Funktionen auf einer Kopie der eigentlichen XML-Instanz angewendet. Dies machen alle Zeilenfunktionen (auch zum Beispiel upper) so, sie berechnen das Ergebnis und ersetzen den Ausgangswert, falls kein Fehler aufgetreten ist. Das ist zunächst einmal eine ganz unproblematische Sache, nur ist eine XML-Instanz im Zweifel größer, vielleicht sogar sehr viel größer als eine einfache Zahl. Dann fällt diese Eigenschaft ins Gewicht. Ob die entstehende XML-Instanz anschließend die gesamte XML-Instanz in der Datenbank überschreibt oder nicht, hängt ein wenig davon ab, wie die Datenbank administriert ist.

Kurzer Exkurs: Speicherformen von XML in der Datenbank
Grundsätzlich können XML-Instanzen nicht nur als clob in der Datenbank abgelegt sein (es bleibt dabei, es handelt sich um XML, aber die Speichersystematik ist im einfachsten Fall ein clob), sondern es ist auch möglich, eine XML-Tabelle auf einer XML-Schema-Definition-(XSD-)Datei beruhen zu lassen. Diese Dateien, die ja die Struktur
einer XML-Instanz beschreiben, können durch Annotationen (Attribute aus dem Namensraum Oracle XML DB) so erweitert werden, dass der Datenbank bekannt gemacht wird, wie eine solche XML-Instanz in einem Netz objektrelationaler Tabellen gespeichert werden soll. Grob gesagt, wird bei diesem Vorgehen eine XML-Instanz in seine Einzelteile geschreddert und verteilt auf ein Netz aus von Oracle angelegten Tabellen gespeichert. 
Ist eine XML-Instanz so gespeichert, würde die update-Anweisung dann tatsächlich nur die betroffenen Tabellen aktualisieren. Durch die neue Speicherungsform XMLBinary seit Datenbankversion 11.2 ist es allerdings unwahrscheinlicher geworden, dass Sie tatsächlich eine objektrelationale Speicherung von XML-Instanzen vornehmen lassen. Bei dieser Speicherform wird eine optimierte, geparste Binärdarstellung der XML-Datei gespeichert, die zudem, wenn ein Schema referenziert wurde, bereits validiert ist. Die Kombination aus dieser Speicherform und dem zugehörigen Indextyp XMLIndex ist extrem schnell und macht eine objektrelationale Speicherung im Regelfall unnötig.

Bleiben wir aber beim Regelfall: Eine XML-Instanz ist, technisch gesehen, ein intelligenter clob, der durch die folgenden Funktionen für uns feingranular geändert wird. Diese Änderung wird allerdings an einer Kopie der XML-Instanz durchgeführt und durch die update-Anweisung die bestehende XML-Instanz vollständig ersetzen.

Allen XML-Funktionen aus dieser Gruppe kann ein optionaler Parameter den Namensraum des Elements übergeben. Sie wissen: 90% aller Fehler beim Umgang mit XML sind auf zwei Fehlerquellen zurückzuführen: XPath-Ausdrücke und Namensraumprobleme. Daher sollten Sie diese Option immer im Hinterkopf behalten, damit Sie nicht Stunden mit der Fehlersuche verbringen. Ist ein Element in einem anderen als dem null-Namensraum (anders gesagt, wenn also überhaupt ein Namensraum angegeben ist, egal, ob als default namespace oder mit einem Namensraumpräfix), müssen Sie diesen Namensraum auch übergeben, ansonsten finden Sie nichts. Der Namensraumparameter wird als Letztes den Funktionen übergeben.

Im Überblick zeige ich Ihnen in der folgenden Tabelle nun einmal die Funktionen, die wir für die einzelnen Bereiche zur Verfügung haben.

Einsatzbereich Funktion
Ändern bestehender XML-Knoten updatexml
Löschen bestehender XML-Knoten deletexml
Einfügen neuer XML-Knoten appendchildxml
insertchildxml
insertchildxmlbefore
insertchildxmlafter
insertxmlbefore
insertxmlafter

Tabelle: Liste der verfügbaren XML-Funktionen zum Bearbeiten von XML

Grundsätzlich gilt: Auch, wenn es anders möglich ist, sollten Sie diese Funktionen für die angegebenen Aufgaben benutzen. Es ist zum Beispiel möglich, mit updatexml neue Knoten in einen Baum einzufügen, allerdings ist dies weniger effizient, als die darauf spezialisierten Funktionen zu benutzen. Zudem haben Sie mit den spezialisierten Funktionen mehr Kontrolle über den Prozess.

Eine weitere Gemeinsamkeit: Die Funktionen unterstützen XPath-Angaben in Version 1.0 (Stand Oracle 11.2), und auch das nicht vollständig. Sie müssen testen, welche Möglichkeiten für Oracle akzeptabel sind und was nicht geht.

Bestehende XML-Instanzen ändern

Beginnen wir damit, dass wir den Beruf eines Mitarbeiters aktualisieren möchten. Herr King soll fortan PRÄSIDENT sein. Hierfür verwenden wir die Funktion updatexml:

update dept_emp_xml
          set liste = updatexml(liste,
             '/Mitarbeiterliste/Mitarbeiter[3]/Beruf/text()',
             'PRÄSIDENT')
      where deptno = 10;


1 Zeile wurde aktualisiert.

Code-Beispiel: Aktualisierung eines XML-Knotens

Leider ist die Formatierung aufgrund des langen XPath-Ausdrucks etwas unschön. Was habe ich gemacht? Zunächst habe ich mit der Funktion updatexml eine aktualisierte Version unserer XML-Instanz angefordert. Dazu wird die aktuelle Version der XML-Instanz als Spalte der Funktion übergeben. Der zweite Parameter stellt einen XPath-Ausdruck dar, der innerhalb der XML-Instanz den Knoten definiert, der durch die Anweisung geändert werden soll. Beachten Sie bitte, dass der XPath-Ausdruck die XPath-Funktion text() enthält, so dass lediglich der Textknoten des Elements geliefert wird. Wäre dies nicht so, lieferte der Ausdruck das komplette Element Beruf mit Inhalt zurück. Daher wird als dritter Parameter der Funktion nun ein Text erwartet, nicht jedoch ein XML-Element. Wäre andererseits der XPath-Ausdruck so gestaltet, dass ein Knoten identifiziert würde, müsste auch eine XMLType-Instanz geliefert werden, die statt des vorhandenen Knotens eingefügt würde. Diese Funktion ist so etwas wie das Schweizer Taschenmesser unter dieser Gruppe von Funktionen, denn es sind noch eine Reihe weiterer Möglichkeiten gegeben: Dieser Funktion können nämlich auch mehrere XPath-Wertpärchen übergeben werden und damit in einem Rutsch mehrere Knoten der XML-Instanz aktualisiert werden. Hierfür ein Beispiel, ich zeige nun die Aktualisierung mit XMLType:

update dept_emp_xml
       set liste =
          updatexml(
             liste,
             '//Mitarbeiter[3]/Beruf',
             XMLType('<Beruf>PRÄSIDENT</Beruf>'),
             '//Mitarbeiter[2]/Name',
             XMLType('<Name>Müller</Name>'))
      where deptno = 10;


1 Zeile wurde aktualisiert.

Code-Beispiel: Aktualisierung von zwei Knoten in einer Anweisung

Ich habe hier die Kurzform // als XPath-Ausdruck gewählt, um irgendein Kindelement Mitarbeiter in der XML-Instanz zu finden. Das hat natürlich lediglich optische Gründe, damit ich die langen XPath-Ausdrücke etwas kürzer halten kann. Wenn Sie etwas mehr Platz zur Eingabe einer solchen Funktion haben, wird das Ganze auch übersichtlicher, etwa so, wie auch eine decode-Funktion übersichtlich sein kann. Als Vorteil verbuchen wir, dass wir mit Hilfe dieser Funktion nicht mehr die gesamte XML-Instanz lesen, parsen und mit Code aktualisieren müssen. Als Nachteil verbuchen wir, das genau das nun die Datenbank tun muss, denn XMLType können Sie im Gebrauch immer gleich dem DOM-Baum setzen. Daher sind die Kosten für das Arbeiten mit solchen Strukturen umso höher, je größer die XML-Instanzen sind. Aber, so viel sollte auch klar sein: XMLType ist kein Ersatz für ein relationales Datenmodell. Manchmal kann die Speicherung von Informationen in XMLType in der Datenbank sehr, sehr gut sein, zum Beispiel bei narrativen Texten, die sich anders schlicht nicht gut speichern lassen und die dennoch gut indizier- und durchsuchbar bleiben sollen. Doch immer dann, wenn strukturierte Informationen mittels XML angeliefert werden sollen, ist eine strukturierte Speicherung in relationalen Tabellen wohl günstiger, weil schneller und weniger speicherintensiv. XML ist, neben vielen Vorzügen, nun auch einmal extrem geschwätzig. Aus diesem Grund sollte die Aktualisierung von XML-Instanzen mit diesen Funktionen auch nicht ebenso häufig nötig sein müssen wie die Aktualisierung einer strukturierten Information in einer Tabelle.

Eine andere Anmerkung: Theoretisch können Sie mit dieser updatexml-Funktion auch die Struktur einer XML-Instanz verändern. Damit meine ich, dass Sie grundsätzlich auch Teilbäume einer XML-Instanz umhängen oder Elementnamen austauschen könnten. Doch dürfte es hier im Regelfall günstiger sein, XSLT für solche Umformungen zu verwenden. Diese Funktionsgruppe, die wir hier beschreiben, dient vor allem der zielgenauen Veränderung von XML-Elementwerten. Hierzu vielleicht ein Szenario: In einer nachrichtenbasierten Anwendung habe ich einmal die Nachrichten selbst über eine SQL-Abfrage erzeugen lassen. Aus irgendwelchen Gründen konnte ich gewisse Elementwerte erst später für diese XML-Nachricht definieren. Das ist dann sehr gut über eine einfache XML-Anweisung mit updatexml möglich.

Löschen vorhandener Elemente

Die Funktion zum Löschen von Elementen ist recht einfach: Sie übergeben der Funktion deletexml die XML-Instanz, einen XPath-Ausdruck und, wenn Sie dies benötigen, einen Namensraum. Anschließend ist der Knoten halt weg. Hier ein kurzes Beispiel für die Anwendung. Aus Abteilung 10 soll der Präsident aus dem XML entnommen werden:

update dept_emp_xml
          set liste = deletexml(liste,
               '//Mitarbeiter[Name="King"]')
     where deptno = 10;


   1 Zeile wurde aktualisiert.


select liste
        from dept_emp_xml
    where deptno = 10;


LISTE
----------------------------------------------------
<Mitarbeiterliste abteilung="10">
    <Mitarbeiter einstellDatum="1981-06-09" id="7782">
        <Name>Clark</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2.450,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="1982-01-23" id="7934">
        <Name>Miller</Name>
        <Beruf>Clerk</Beruf>
        <Gehalt>1.300,00€</Gehalt>
    </Mitarbeiter>
</Mitarbeiterliste>

Code-Beispiel: Löschen eines Elements aus einer XML-Instanz

Einfügen neuer Elemente

Die meisten Funktionen beschäftigen sich mit dem Einfügen neuer Elemente. Allerdings sind viele Funktionen sehr ähnlich, so dass wir uns leicht einen Überblick verschaffen können, indem wir die Strukturen herausarbeiten.

Zunächst einmal unterscheiden wir generell, ob eine Funktion ein Kindelement einfügt oder ein Geschwisterelement. Zum Einfügen eines Geschwisterelements (vor oder nach dem, durch den XPath-Ausdruck angesprochenen Zielelement) verwenden Sie die Funktion insertxml (before oder after). Wenn Sie ein Element unterhalb des Zielelements als Kindelement anlegen möchten, verwenden Sie die Funktion mit dem child im Namen. appendchildxml legt das Kindelement als letztes Kind innerhalb des Zielelements ab, insertchildxml ohne Angabe legt es irgendwo ab (manchmal kann, bei schemabasiertem XML, die Position über das Schema bestimmt werden. Falls nicht, ist die Position zufällig). insertchildxmlbefore oder -after legt das Kindelement an einer Stelle in einer Liste von Elementen ab, die mit der Funktion übergeben werden muss. Das klingt zunächst etwas kompliziert, meint aber Folgendes: Wenn ein XML-Element Mitarbeiter in Mitarbeiterliste häufig auftaucht, dann können Sie bestimmen, dass das neue Element vor (oder hinter) dem zum Beispiel dritten Mitarbeiter der bestehenden Liste eingefügt wird, indem Sie einen XPath-Ausdruck (hier: Mitarbeiter[3]) übergeben, der angibt, vor oder hinter welchem Element der Liste das neue Element eingefügt werden soll.

Fassen wir also zusammen: Wir haben eine Gruppe von Funktionen, um neue Elemente in eine bestehende XML-Instanz einzufügen. Dabei unterscheiden wir, ob ein neues Element als Geschwister vor oder hinter einem bestehenden Element eingefügt werden soll (insertxml) oder ob das neue Element als Kindelement eines bestehenden Elements eingefügt werden soll (insertchildxml). Die Position des Elements kann generell über before und after gesteuert werden, bei Listen von Kindelementen können wird darüber hinaus ein Kindelement als letztes einfügen (appendchildxml) oder irgendwo dazu packen (insertchildxml).

Das vorweg, sind die Funktionen nun nicht mehr sehr schwer zu verstehen. Ich gebe ein Beispiel für die Funktion insertxmlbefore, appendchildxml und insertchildxmlbefore:

-- Ein Geschwisterelement vor dem zweiten Mitarbeiter einfügen
update dept_emp_xml
       set liste =
         insertxmlbefore(liste,
              '//Mitarbeiter[2]',             
         xmltype(
              '<Mitarbeiter einstellDatum="2001-03-12">' ||
              ' <Name>Meier</Name>' ||
              ' <Beruf>Halbkreisingenieur</Beruf>' ||
              ' <Gehalt>3.500,00€</Gehalt>' ||
              '</Mitarbeiter>'))
     where deptno = 10;


LISTE
----------------------------------------------------------------
<Mitarbeiterliste abteilung="10">
    <Mitarbeiter einstellDatum="1981-06-09" id="7782">
        <Name>Clark</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2.450,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="2001-03-12">
        <Name>Meier</Name>
        <Beruf>Halbkreisingenieur</Beruf>
        <Gehalt>3.500,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="1982-01-23" id="7934">
        <Name>Müller</Name>
        <Beruf>Clerk</Beruf>
        <Gehalt>1.300,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="1981-11-17" id="7839">
        <Name>King</Name>
        <Beruf>PRÄSIDENT</Beruf>
        <Gehalt>5.000,00€</Gehalt>
    </Mitarbeiter>
</Mitarbeiterliste>


-- Ein Kindelement als letzes anfügen
update dept_emp_xml
       set liste =
         appendChildXML(liste,
              '/Mitarbeiterliste',
           xmltype(
              '<Mitarbeiter einstellDatum="2001-03-12">' ||
              ' <Name>Meier</Name>' ||
              ' <Beruf>Halbkreisingenieur</Beruf>' ||
              ' <Gehalt>3.500,00€</Gehalt>' ||
              '</Mitarbeiter>'))
where deptno = 10;


LISTE
---------------------------------------------------------------
<Mitarbeiterliste abteilung="10">
    <Mitarbeiter einstellDatum="1981-06-09" id="7782">
        <Name>Clark</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2.450,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="1982-01-23" id="7934">
        <Name>Müller</Name>
        <Beruf>Clerk</Beruf>
        <Gehalt>1.300,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="1981-11-17" id="7839">
        <Name>King</Name>
        <Beruf>PRÄSIDENT</Beruf>
        <Gehalt>5.000,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="2001-03-12">
        <Name>Meier</Name>
        <Beruf>Halbkreisingenieur</Beruf>
        <Gehalt>3.500,00€</Gehalt>
    </Mitarbeiter>
</Mitarbeiterliste>


-- Ein Kindelement vor dem ersten Mitarbeiter einfügen
update dept_emp_xml
      set liste =
          insertChildXMLbefore(liste,
              '/Mitarbeiterliste',
              'Mitarbeiter[1]',
              xmltype(
              '<Mitarbeiter einstellDatum="2001-03-12">' ||
              ' <Name>Meier</Name>' ||
              ' <Beruf>Halbkreisingenieur</Beruf>' ||  
              ' <Gehalt>3.500,00€</Gehalt>' ||
              '</Mitarbeiter>'))
    where deptno = 10;


LISTE
----------------------------------------------------------------
<Mitarbeiterliste abteilung="10">
    <Mitarbeiter einstellDatum="2001-03-12">
        <Name>Meier</Name>
        <Beruf>Halbkreisingenieur</Beruf>
        <Gehalt>3.500,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="1981-06-09" id="7782">
        <Name>Clark</Name>
        <Beruf>Manager</Beruf>
        <Gehalt>2.450,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="1982-01-23" id="7934">
        <Name>Müller</Name>
        <Beruf>Clerk</Beruf>
        <Gehalt>1.300,00€</Gehalt>
    </Mitarbeiter>
    <Mitarbeiter einstellDatum="1981-11-17" id="7839">
        <Name>King</Name>
        <Beruf>PRÄSIDENT</Beruf>
        <Gehalt>5.000,00€</Gehalt>
    </Mitarbeiter>
</Mitarbeiterliste>

Code-Beispiel: Einige Einfügeoperationen für XML-Elemente

Die Dinge, die Sie beachten sollten, sind: In der ersten update-Anweisung wird der XPath bis auf das Geschwisterelement angegeben (also Mitarbeiter), während bei den Funktionen, die Kindelemente einfügen, naturgemäß das übergeordnete Element, also Mitarbeiterliste, angegeben werden muss (geben Sie hier ebenfalls Mitarbeiter an, wird das neue Mitarbeiter-Element in das angegebene Mitarbeiter-Element geschachtelt). Dann wird bei der letzten update-Anweisung zusätzlich zum XPath für das Elternelement auch noch ein XPath-Ausdruck für ein Listenelement, vor oder hinter dem das neue Element eingefügt werden soll, mitgegeben.

Alle update-Anweisungen wurden mit rollback rückgängig gemacht, bevor ich die nächste update-Anweisung ausgeführt habe, nur, damit Sie sich nicht wundern, wo die ganzen Meiers geblieben sind. Ein Halbkreisingenieur reicht ja wohl auch für eine Abteilung ...

  

<< 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 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Oracle 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