Relationale Daten aus XML extrahieren

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

Der umgekehrte Weg zur Erzeugung von XML ist die Extraktion von Daten aus vorhandenem XML. Auch bei diesem Verfahren ist es möglich, mehrere Wege zu gehen:

  • XQuery
  • automatisiertes Schreddern mittels objektrelationaler Tabellen
  • händisches Schreddern mittels Java-Programmierung

Extraktion relationaler Daten mit »XMLTable«

Wir beginnen zunächst wieder mit dem Standard XQuery. Eine (englischsprachige) Einführung in die Integration von XQuery in die Oracle-Datenbank finden Sie im Übrigen im Oracle XML DB Developers Guide: »Using XQuery with Oracle XML DB«. Für unseren Bedarf soll es reichen, zu zeigen, auf welche Weise der Inhalt einer XML-Instanz durch die Funktion XMLTable() entschachtelt werden kann.

Exkurs: XQuery
Ab der Version 10g R2 wurde der Funktionsumfang der SQL/XML-Funktionalität deutlich erweitert. Dies hat insbesondere damit zu tun, dass viele Standards erst zu dieser Zeit so weit entwickelt waren, dass sie als implementierungsfähig angesehen werden konnten. Für die Extraktion von Daten aus bestehenden XML-Instanzen bietet sich die Funktion XMLTable an, die auf XQuery basiert und erst spät zum Kanon der XML-Funktionalität hinzugestoßen ist. Mithilfe dieser Funktion ist es möglich, in bestehenden XML-Instanzen wie in lokalen Datenbankressourcen zu suchen und die Ergebnisse mit einer einfachen Syntax in relationale Strukturen zu überführen.
XQuery stellt eine Abfragesprache für XML-Daten zur Verfügung und ist vom W3C-Konsortium verabschiedet worden. XQuery-Abfragen haben eine mit SQL grob vergleichbare Struktur und Mächtigkeit. Ähnlich, wie SQL im Umfeld relationaler Datenbanken dazu genutzt wird, Teildaten in einer sinnvollen Weise für eine gegebene Aufgabenstellung zusammenzustellen, kann XQuery dazu genutzt werden, ebensolche Datenextraktionen aus XML-Dateien zu erzeugen. Im Produktionsumfeld haben XML-Instanzen eine zum Teil erhebliche Komplexität, was die Anzahl der strukturierten Elemente angeht – ebenso wie die Tiefe der Schachtelung. Eine Abfragesprache, die aus diesen Dokumenten mit einer standardisierten Syntax Teilbäume extrahiert und anschließenden Prozessen zur Verfügung stellen kann, ist sicherlich hochwillkommen.
Die Kerntechnik von XQuery sind Abfragen, die aufgrund der Wahl der Schlüsselwörter For-Let-Where-Order-By-Return zum Akronym FLWOR (gesprochen wie engl. flower) zusammengefasst werden. Eine solche Abfrage kann gegen eine XML-Instanz ausgeführt werden und, ähnlich wie SQL in Bezug auf relationale Tabellen, dort Elemente filtern, berechnen, neu zusammenstellen und ausgeben. Die einzelnen Klauseln der FLWOR-Abfrage haben ihre grobe Entsprechung in den Klauseln einer select-Anweisung, nur dass die Datenbasis eine XML-Instanz darstellt und die Navigation in dieser Instanz mithilfe von XPath-Ausdrücken erfolgt. Die Ergebnismenge ist eine sogenannte Sequenz von XML-Elementen. Diese Sequenz muss nicht notwendigerweise (und wird es im Regelfall auch nicht) wohlgeformtes XML sein, vielmehr sind XML-Fragmente oder gar skalare Werte zu erwarten, die durch weitere Prozesse bearbeitet werden. Diese Ausrichtung macht XQuery zum Mittel der Wahl, wenn es um die Verarbeitung bereits als XML in der Datenbank vorliegender Daten geht, obwohl theoretisch auch die Erzeugung von XML aus relationalen Daten mit XQuery möglich ist.

Die grundsätzliche Arbeitsweise der Funktion XMLTable besteht darin, die ihr übergebene XML-Instanz durch eine XQuery-Abfrage zu bearbeiten und die durch die Abfrage generierten Daten zurückzuliefern. Im vielleicht einfachsten Fall ist die XQuery-Abfrage ein XPath-Ausdruck, also einfach ein Zeiger auf den Teilbaum, der durch die Abfrage ermittelt werden soll. Sehen wir uns für diese Anwendung ein Beispiel an. Dazu erzeuge ich mir zunächst aus einer der bereits besprochenen SQL/XML-Abfragen eine Tabelle, die mir als XML-Quelle dienen soll:

create table emp_xml as
   select xmlelement("Mitarbeiterliste",    
      xmlagg(
         xmlelement("Mitarbeiter",
             xmlattributes(
                 empno "mitarbeiterId",
                 hiredate "einstellDatum"),
             xmlforest(
                 initcap(ename) "Name",
                initcap(job) "Beruf",
                sal "Gehalt",
                comm "Boni")
        
      order by ename)
     ) xml_content
   from emp;


Tabelle wurde erstellt.

Dieses Mal verzichte ich auf die Gruppierung nach Abteilung, damit meine kleine Tabelle eine etwas fülligere XML-Instanz ergibt. Interessant ist vielleicht zudem, wie innerhalb der xmlagg-Funktion in Zeile 12 noch ein Sortierkriterium angegeben werden kann, um die Daten vor der Gruppierung noch zu sortieren. Da wir nun also XML-Daten haben, können wir uns daranmachen, die Daten wieder relational zu entschachteln:

select upper(v.ename) ename, upper(v.job) job, v.sal, v.comm
    fromemp_xml x,
       XMLTable('/Mitarbeiterliste/Mitarbeiter'   
          passing x.xml_content
          columns
          empno number path '@mitarbeiterId',
          hireadate date path '@einstellDatum',
          ename varchar2(20 char) path 'Name',
          job varchar2(20 char) path 'Beruf',
         sal number path 'Gehalt',
         comm number path 'Boni') v;


ENAME       JOB        SAL     COMM
---------- ---------- ------- -------
ADAMS       CLERK      1100       
ALLEN       SALESMAN   1600    300
BLAKE       MANAGER    2850
CLARK       MANAGER    2450
FORD        ANALYST    3000
JAMES       CLERK      950
JONES       MANAGER    2975
KING        PRESIDENT  5000
MARTIN      SALESMAN   1250    1400
MILLER      CLERK      1300
SMITH       CLERK      800
SCOTT       ANALYST    3000
TURNER      SALESMAN   1500    0
WARD        SALESMAN   1250    500
14 Zeilen ausgewählt.

Code-Beispiel: Extraktion von XML mittels »XMLTable«-Anweisung

Die erste Klausel der XMLTable-Anweisung legt fest, welche XML-Instanz zum Extrahieren der Daten verwendet werden soll. Wie gesagt, habe ich hier einfach einen XPath-Ausdruck eingefügt, alternativ wäre eine komplette XQuery-Abfrage möglich gewesen. Die nachfolgende passing-Klausel legt fest, auf welche Daten sich der XPath-Ausdruck oberhalb beziehen soll. Hier reiche ich von außen über das Tabellenalias x die Spalte xml_contentXML aus der Tabelle emp_xml in die Funktion hinein, ähnlich einer harmonisierten Unterabfrage. Alle weiteren Angaben beziehen sich nun auf das Ergebnis der XPath-Anweisung, in meinem Fall auf ein XML-Fragment mit vielen Mitarbeiter-Knoten als Elementen auf der obersten Ebene. Nun folgt das Mapping der XML-Elemente auf Spalten und SQL-Datentypen. Die Schreibweise mittels der columns-Klausel und der XPath-Angaben halte ich persönlich für sehr intuitiv. Der XMLTable-Funktion wird nun noch ein Alias gegeben, und anschließend kann auf die Daten wie auf eine relationale Tabelle zugegriffen werden.

Aufwendiger, aber auch leistungsfähiger sind die Optionen zur Extraktion von Daten mittels ganzer XQuery-Abfragen, obwohl das Ergebnis der Extraktion im Regelfall selbst wieder XML-Instanzen sind. Hier soll nur eine stellvertretende Abfrage stehen, denn XQuery selbst ist mächtig und definitiv außerhalb des Fokus dieses Buches. Dennoch möchte ich Ihnen zumindest einen optischen Eindruck von einem FLWOR-Ausdruck vermitteln:

select v.*
    from emp_xml x,   
       XMLTable('for $i in /Mitarbeiterliste/Mitarbeiter
          where $i/Beruf eq "Salesman"
          return $i'
        passing x.xml_content
        columns
          empno number path '@mitarbeiterId',
          hireadate date path '@einstellDatum',
         ename varchar2(20 char) path 'Name',
         job varchar2(20 char) path 'Beruf',
         sal number path 'Gehalt',
         comm number path 'Boni') v;


EMPNO    HIREADAT     ENAME      JOB        SAL        COMM
------- ----------- ---------- ---------- ---------- ----------
7499      20.02.81    Allen    Salesman     1600       300    
7654      28.09.81    Martin   Salesman     1250       1400
7844      08.09.81    Turner   Salesman     1500       0
7521      22.02.81    Ward     Salesman     1250       500

Code-Beispiel: Beispiel für eine XQuery-Anweisung

Wie Sie sehen, ist XQuery geeignet, eine ähnliche Funktionalität wie SQL bezogen auf XML-Daten zu übernehmen. Ein Tipp für die Einarbeitung in XQuery ist das Buch »XQuery – Grundlagen und fortgeschrittene Methoden« von Wolfgang Lehner und Harald Schöning aus dem dpunkt Verlag. Falls Sie noch die Extraktion von Daten mittels extract und extractValue kennen und hier vermissen: Dieser Weg ist seit Oracle 11.2 deprecated.

Extraktion relationaler Daten mittels Objektorientierung

Um die Details dieser Extraktionsweise zu verstehen, benötigen wir etwas Vorwissen, das ich erst unter Die XML-Datenbank vollständig darstellen kann. Daher möchte ich die Informationen von dort nicht bereits hier vorwegnehmen, sondern das Prinzip erläutern. Die Details reiche ich dann nach.

Zunächst ist es erforderlich, ein XML-Schema in der Datenbank zu registrieren. Dieses Schema wird in die XML-DB eingestellt und über einen Namensraum der Datenbank bekannt gemacht. Entweder verwenden Sie dafür den Namensraum, der im Schema als targetNamespace vereinbart wurde, oder aber den Speicherpfad innerhalb der XML-DB. Das Schema wird vor der Registrierung durch zusätzliche Attribute aus dem Namensraum xmlns:xdb="http://xmlns.oracle.com/xdb" erweitert und annotiert. Der Sinn: In diesem Namensraum sind Attribute definiert, die XML-Elemente auf Objekttypen oder SQL-Datentypen abbilden helfen. So könnte zum Beispiel für einen Teilbaum der XML-Struktur festgelegt werden, dass dieser Teilbaum als Instanz des Typs OBJECT_T innerhalb der Datenbank abgelegt werden soll oder dass das Element Mitarbeiter als varchar2(30)-Feld gespeichert werden soll. Durch dieses Mapping von XML-Elementen auf SQL-Datentypen werden also die Abbildungsregeln auf ein objektrelationales Modell definiert.

Bei der Registrierung des Schemas können durch das Package dbms_xmlschema gleichzeitig auch die objektrelationalen Tabellen, Objekttypen und sogar JavaBeans zum Zugriff auf die Objekttypen angefordert werden. Die gesamte Speicherstruktur innerhalb der Datenbank wird also über die XML-Schemainstanz definiert und durch den Registrierungsprozess gleich auch erstellt.

Anschließend kann eine insert-Anweisung einer XML-Instanz auf diese objektrelationale Tabelle durchgeführt werden. Dies hat dann zur Folge, dass Oracle die XML-Instanz in ein entsprechendes Objekt umrechnet und in die passende objektrelationale Tabellenlandschaft einfügt. Anschließend sind die Daten über select-Anweisungen gegen die objektrelationale Importtabelle direkt verfügbar.

Daten auf diese Weise in die Datenbank einzufügen dauert etwas länger, als die Daten einfach nur in eine CLOB-Spalte zu packen, und es dauert auch länger, als die Daten in eine BinaryXML-Spalte zu schreiben. Der Zugriff ist anschließend jedoch deutlich schneller als bei der Speicherung in der CLOB-Spalte, jedoch nicht wesentlich schneller als der Zugriff auf eine BinaryXML-gespeicherte XML-Instanz. Daher hat dieser Weg auch durch diese Eigenschaft von BinaryXML etwas an Glanz verloren. Die Alternative wäre eine objektrelationale View auf eine BinaryXML-Spalte, mit wahrscheinlich ähnlichen Zugriffszeiten.

Ohne hier in die Tiefe gehen zu wollen, möchte ich dennoch den einen oder anderen Grund für meine Ablehnung der objektrelationalen Speicherung größerer Datenmengen nennen:

  • Der Zugriff auf geschachtelte Informationen innerhalb eines Objekts ist ausschließlich im Rahmen eines Objekts möglich. Haben Sie also zum Beispiel eine Bestellung mit Bestellpositionen, können Sie auf diese nur über die Bestellung zugreifen. Eine Auswertung, welche Artikel wie oft bestellt wurden, ist demnach direkt über die geschachtelte Tabelle unmöglich. Das widerspricht meinen Erwartungen an eine relationale Datenbank, die einen ungehinderten Zugriff auf alle Daten vorsieht.
  • update-Anweisungen gegen eine solche objektrelationale Tabelle sind aus dem gleichen Grund aufwendig, da jede Bestellposition als in einer eigenen Tabelle befindlich angesehen wird, obwohl sie es de facto nicht ist. Auch hier ist es wieder diese Art von Kanalisierung des Zugriffs, die mich stört.
  • Da Oracle ebenfalls nichts anderes macht, als ein relationales Datenmodell, dafür aber besondere Einschränkungen bereithält, ist es aus meiner Sicht einfacher, die erforderlichen Tabellen selbst anzulegen. Das erhöht die Kontrolle über die Tabellenstrukturen und ist konform zu der sonstigen Sichtweise, dass die Datenmodellierung Ergebnis eines bewussten Modellierungsprozesses sein und nicht durch Automatismen erzeugt werden sollte.
  • Die Speicherung erfordert relativ viel Platz, weil der Verweis der Daten aufeinander durch ein Netz herkömmlicher, relationaler Tabellen durchgeführt wird. Der Schlüssel ist dabei eine OID (Objekt-ID), die immerhin 16 Byte breit ist und daher größer sein dürfte als die allermeisten normalen Schlüssel. Schlüssel dieser Art werden bei allen verbundenen Objektinstanzen verwendet.

Den letzten Punkt können wir uns einmal ansehen, denn die Funktion sys_op_guid() erzeugt eine solche OID:

select sys_op_guid() oid, dump(sys_op_guid()) dump_oid
   from dual;


OID
--------------------------------
DUMP_OID
--------------------------------------------------------------
CDAAD9F162FF4372961E0DDF6DD66A2B
Typ=23 Len=16: 32,163,204,141,183,221,68,78,130,28,254,63,103,136,205,134

Code-Beispiel: Anzeige einer OID mittels der Funktion »sys_op_guid«

Extraktion relationaler Daten mittels Java-Programmierung

Hier kann ich natürlich nur eine generelle Richtung beschreiben: Die Idee besteht darin, die XML-Instanz einmal seriell zu lesen und für jedes gefundene Element einen entsprechenden Event zu werfen. Diese Events werden dann analysiert und durch Mapping-Tabellen auf einen oder mehrere Prepared Statements (SQL-Anweisungen, die mittels Parametern konfiguriert werden) verteilt, die jeweils eine insert-Anweisung in eine der Zieltabellen repräsentieren. Ist ein Prepared Statement komplett mit Parametern belegt, wird die Anweisung abgeschickt und in die Datenbank eingefügt.

Dieses grundlegende Vorgehen lässt Raum für mannigfaltige Optimierungen. Ziel ist es, die Anzahl der Roundtrips zum Datenbankserver zu minimieren. Dies kann einerseits dadurch geschehen, dass die Anweisungen an die Datenbank im Batch abgeschickt werden, andererseits durch kreative Programmierung im Umfeld der Versorgung mit Primärschlüsselwerten. Dann sollten in diesem Zusammenhang deferrable constraints verwendet werden, um Kinddatensätze vor den Eltern einfügen zu können. Schließlich kann der gesamte Java-Code innerhalb der Datenbank ausgeführt werden (nicht in Oracle XE, weil keine Java Virtual Machine (JVM) im Datenbank-Kernel vorhanden ist) und so die Netzwerklaufzeiten weiter minimiert werden. Der Zugriff erfolgt dann über eine PL/SQL-Wrapper-Methode.

Im Ergebnis ist mit diesem Verfahren eine sehr hohe Performance zu erzielen: Das Ganze geht extrem schnell, verbraucht konstant wenig Arbeitsspeicher, weil kein DOM-Baum erzeugt wird, und nutzt die zur Verfügung stehenden Mechanismen (Transaktionsschutz der Datenbank, Validierung durch den SAX-Parser, Optimierungen im Java-Treiber etc.) voll aus. Zudem ist die Programmierung im Grundsatz immer gleich, unabhängig von den XML-Instanzen, die gelesen, und den Tabellen, auf die die Daten übertragen werden müssen. Daher können mit einem Code-Generator weite Teile des Codes automatisiert erzeugt werden – bis hin zu einer Option, die das Mapping der XML-Elemente auf Tabellenspalten aus externen Quellen parametrierbar macht und die entsprechenden Mapper selbstständig erzeugt.

Trotz all dieser Vorteile ist die Programmierung in Java selbstverständlich aufwendiger als der Einsatz zum Beispiel von XQuery und auch weniger wartungsfreundlich, zudem spielt dieser Ansatz seine Stärke in wenig oder nur mittelmäßig komplexen XML-Strukturen aus, wie sie andererseits aber für nachrichtenbasierte Systeme üblich sind.

  

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