Die zunehmende Verbreitung von XML als Datenaustauschformat macht sowohl das Erstellen von XML aus Datenbanktabellen als auch die Übernahme von XML in dieselben zu einer Standard-Anforderung der täglichen Praxis. Dieser Artikel stellt anhand eines praktischen Beispiels vor, wie eine XML-Schnittstelle auf relationale Tabellen direkt in der Datenbank hinterlegt wird. Zur Umsetzung kommen Standards wie SQL/XML und Datenbank-Technologien wie Views und Trigger zum Einsatz. Zwar beziehen sich die vorgestellten SQL-Skripte auf die Oracle-Datenbank, sie können jedoch in jedem RDBMS, das den SQL/XML-Standard, Views und INSTEAD-OF Trigger unterstützt, nachvollzogen werden. Natürlich lässt sich das Konzept auch abwandeln: Anstelle von Views können auch Stored Procedures verwendet werden.
Als Ausgangssituation dient ein einfaches Datenmodell für Wertpapierdepots (Abbildung 1). Es enthält drei Tabellen: Ein Kunde kann null bis viele Wertpapierdepots besitzen und in jedem Depot können null bis viele Wertpapierpositionen enthalten sein. Das XML-Format ist in Abbildung 2 dargestellt. Die XML-Schnittstelle muss dieses Format demnach sowohl erzeugen als auch verarbeiten können.

Abb. 1: Relationale Tabellen

Abb. 2: XML-Austauschformat
XML erzeugen: Von einfachen Strukturen ...
Im ersten Schritt wird das Generieren der XML-Dokumente betrachtet. Dazu kommen die Funktionen des SQL/XML-Standards [1] zum Einsatz. Sie dienen zum Erzeugen von XML mit SQL und sind Bestandteil des ANSI-SQL-Standards (SQL:2003).
- XMLElement(): erzeugt ein XML-Tag
- XMLAttributes(): erzeugt ein oder mehrere XML-Attribute
- XMLComment(): erzeugt einen XML-Kommentar
- XMLRoot(): generiert den XML-Prolog
- XMLAgg(): dient zum Erzeugen von Hierarchien
Die SQL/XML-Funktionen werden wie gewöhnliche SQL-Funktionen eingesetzt und können natürlich auch mit solchen kombiniert werden. Sie verwenden intern keine DOM-Objekte und arbeiten auch mit großen Datenmengen extrem performant.
select XMLElement("Gruss", 'Hallo Welt') TEXT
from dual
TEXT
-------------------------
<Gruss>Hallo Welt</Gruss>
Listing 1: Einfache XML-Sicht auf die Wertpapier-Tabelle create view WERTPAPIER_XML_VIEW as
select
XMLRoot(
XMLElement("wertpapier",
XMLAttributes(
wp.ISIN as "isin",
dp.KONTONR as "depotnummer"
),
XMLElement("bezeichnung", wp.BEZEICHNUNG),
XMLElement("stueck_nominale", wp.STUECK_NOM)
)
) as XML
from WERTPAPIER_TAB wp join DEPOT_TAB dp
Listing 1 zeigt den konkreten Einsatz der SQL/XML-Funktionen. Es erzeugt aus der Tabelle WERTPAPIER_TAB für jede Zeile ein (einfaches) XML-Dokument. Als Datentyp liefert die so erzeugte View einen XMLTYPE zurück. Es ist für den Nutzer der View nicht erkennbar, ob die XML-Dokumente tatsächlich als XMLTYPE
in der Datenbank gespeichert sind oder ob sie aus relationalen Daten generiert wurden.
SQL> select XML from WERTPAPIER_XML_VIEW
XML
--------------------------------------------------------
<wertpapier isin="US00010281" depotnummer="500917211">
<bezeichnung>Oracle Corp</bezeichnung>
<stueck_nominale>1000</stueck_nominale>
</wertpapier>
<wertpapier isin="DE01020911" depotnummer="500917211">
<bezeichnung>Bundesanleihe 10/2010</bezeichnung>
:
... hin zu komplexen Strukturen
In der Praxis sind meist komplexere, hierarchische XML-Strukturen gefordert. Auch diese lassen sich mit den SQL/XML-Funktionen erzeugen. XMLAgg() fügt eine neue Hierarchieebene ein. Das Beispiel in Listing 2 erzeugt zwei Hierarchieebenen, wobei jedes XMLAgg() in eine Unterabfrage (
Sub-Select) eingebettet ist.
Listing 2: Erstellung komplexer XML-Dokumente mit den SQL/XML-Funktionen create view KUNDE_XML_VIEW as
select
XMLElement("kunde",
XMLAttributes(kd.KUNDE_ID as "kundennummer"),
XMLElement("name", kd.NAME),
XMLElement("vorname", kd.VORNAME),
XMLElement("ort", kd.ORT),
(
select
XMLAgg(
XMLElement("depot",
XMLAttributes(dp.KONTONR as "kontonummer"),
(
select
XMLAgg(
XMLElement("wertpapier",
XMLAttributes(wp.ISIN as "isin"),
XMLElement("bezeichnung", wp.BEZEICHNUNG),
XMLElement("stueck_nominale", wp.STEUCK_NOM)
))
from WERTPAPIER_TAB wp where depot_id = dp.depot_id
)))
from DEPOT_TAB dp where dp.kunde_id = kd.kunde_id
)) as XML
from KUNDE_TAB kd
Es ist einleuchtend, dass die SQL-Abfrage mit zunehmender Komplexität der zu erzeugenden XML-Dokumente immer länger und unübersichtlicher wird. In der Praxis empfiehlt es sich daher, nach dem "Bausteinprinzip" vorzugehen - also zunächst Views für kleine XML-Dokumente aus einzelnen Tabellen zu erzeugen und diese dann in anderen Views zusammenzufassen. Da virtuelle XML-Dokumente aus einer XML-View tatsächlich gespeicherten gleichgestellt sind, können Operationen wie Stylesheet-Transformationen (XMLTRANSFORM) oder Datenextraktion (EXTRACTVALUE) auch mit XML-Views durchgeführt werden. Daraus kann dennoch eine relationale Übersicht über alle Depots eines Kunden angefordert werden.
select
extractvalue(kd.XML, '/kunde/name') name,
extractvalue(value(dp), '/depot/@kontonummer') dpnr
from KUNDE_XML_VIEW kd,
table(xmlsequence(extract(kd.XML, '/kunde/depot'))) dp
NAME DPNR
--------------------- --------------------
Mustermann 500917211
Meier 500189212
: :
Der Vorteil liegt auf der Hand. Da die Datenbank die View-Definition kennt, kann der Optimizer ein
Query Rewrite durchführen. Die Datenbank führt also tatsächlich eine (wesentlich performantere) relationale Abfrage aus. Obwohl die relationalen Tabellen dem Nutzer verborgen bleiben, profitiert er dennoch davon.
XML abrufen? Ganz einfach mit HTTP!
Mit dem Erstellen der Views ist der erste Teil der XML-Schnittstelle abgeschlossen. Die relationalen Tabellendaten können nun als XML-Dokumente aus der Datenbank abgerufen werden. Dies ist in der Oracle-Datenbank übrigens nicht nur per SQL, sondern auch per HTTP-Protokoll möglich. Eine standardmäßig installierte Oracle-Datenbank "lauscht" auf Port 8080 auf HTTP-Anfragen. Mit einem speziell formulierten URL kann die XML-View aus Listing 2 direkt angesprochen werden (Abbildung 3): http://[host]:8080/oradb/[DB-USER]/[VIEW-NAME]

Abb. 3: XML per HTTP abrufen
XML entgegennehmen
Die vollständige XML-Schnittstelle muss XML auch entgegennehmen können. Wird ein XML-Dokument gespeichert, so soll die Datenbank dessen Inhalt auf die relationalen Tabellen verteilen.
Versucht man, ein XML-Dokument per SQL INSERT in die View einzufügen, so erhält man eine Fehlermeldung. Der Grund dafür ist die fehlende Update-Fähigkeit der View (siehe Kasten „DML auf Views“).
SQL> insert into KUNDE_XML_VIEW values ('<kunde>...');
insert into KUNDE_XML_VIEW values ('<kunde>...')
*
ERROR at line 1:
ORA-01733: virtual column not allowed here
Speziell zu diesem Zweck wurden in der Oracle-Datenbank die INSTEAD-OF-Trigger [3] eingeführt. Sie sind seit der Version 8 verfügbar, werden auf Views angelegt und
anstelle eines INSERT, UPDATE oder DELETE ausgeführt. So lässt sich jede View updatefähig machen - der Entwickler entscheidet selbst, was bei einer DML-Operation geschehen soll.
Listing 3: Deklaration und „Gerüst“ eines INSTEAD-OF-Triggers create or replace trigger tr_kunde_xml_view
instead of insert or update or delete
on kunde_xml_view for each row
declare
begin
if inserting then
...
end if;
if updating then
...
end if;
if deleting then
...
end if;
end;
Der Aufbau eines INSTEAD-OF-Triggers ist in Listing 3 dargestellt. Im Gegensatz zu einem normalen Datenbanktrigger enthält er in Zeile 2 die Schlüsselworte INSTEAD OF. Im Trigger-Body können nun unterschiedliche Aktionen für die verschiedenen DML-Ereignisse implementiert werden. Das neu eingefügte oder geänderte XML-Dokument wird durch das Präfix :new referenziert, das "alte" durch :old.
Die folgenden Beispiele gehen auf die Aktionen des INSTEAD-OF-Triggers für ein INSERT ein - UPDATE und DELETE können dann analog implementiert werden.
Zunächst wird die "Root"-Ebene des XML-Dokuments in die relationale Tabelle KUNDE_TAB übernommen (Listing 4). Informationen zum Kunden kommen im XML-Dokument nur einmal vor. Die Übernahme erfolgt durch ein einfaches SQL INSERT-Kommando, wobei die Werte mit der EXTRACTVALUE-Funktion aus dem neu eingefügten XML-Dokument (:new) extrahiert werden. Welcher XML-Knoten von EXTRACTVALUE angesprochen wird, richtet sich nach dem übergebenen XPath-Ausdruck. Da die Kundennummer für die weitere Verarbeitung der Depot- und Wertpapierinformationen noch gebraucht wird, wird sie in eine Variable (v_kundeid) übernommen.
Listing 4: INSTEAD-OF-Trigger - Bearbeitung der Root-Ebene :
if inserting then
insert into kunde_TAB (
kunde_id, name, vorname, ort)
values (
extractvalue(:new.XML, '/kunde/@kundennummer'),
extractvalue(:new.XML, '/kunde/name'),
extractvalue(:new.XML, '/kunde/vorname'),
extractvalue(:new.XML, '/kunde/ort')
) returning kunde_id into v_kundeid;
:
end if;
:
Die Informationen zu Depot und Wertpapieren können im XML-Dokument mehrfach vorkommen - daher müssen sie anders behandelt werden.
Listing 5: INSTEAD-OF-Trigger - Übernahme der mehrfach vorkommenden Elemente :
for dp_lst in (
select
extractvalue(value(dp), '/depot/@kontonummer') as ktonr
from table(xmlsequence(extract(:new.XML, '/kunde/depot'))) dp
) loop
insert into DEPOT_TAB (kunde_id, KONTONR)
values (dp_list.ktonr, v_kundeid);
-- Hier eine geschachtelte Schleife für die Verarbeitung
-- der Wertpapier-Informationen<br></br>
end loop;
:
Die Funktion TABLE(XMLSEQUENCE(EXTRACT(...))) extrahiert das XML-Fragment mit den Depot-Informationen und erzeugt daraus eine
Sequenz über die einzelnen Depots (Listing 5). Die Schleife (loop) läuft über diese Sequenz. Die Schleifen können geschachtelt werden.
Nach Erstellung des Triggers ist die XML-Schnittstelle vollständig. Ein SQL INSERT in die View ist nun erfolgreich. Die Inhalte finden sich anschließend in den relationalen Tabellen wieder.
SQL> insert into kunde_xml_view values ('<kunde>...');
1 row created.
Beim Implementieren der Schnittstelle sollte man auch mögliche Konfliktfälle von vorneherein berücksichtigen. Ein neu einzufügender Kunde könnte bereits existieren. Die Frage ist, woran man dies erkennen kann und was passieren soll: Sollen die alten Informationen überschrieben, beibehalten oder soll eine Fehlermeldung ausgelöst werden? Solche Fragen können jedoch nicht pauschal, sondern nur im Einzelfall entschieden werden.
Fazit
Mit der vorgestellten Technik kann man eine XML-Schnittstelle direkt in der Datenbank hinterlegen. Im Vergleich zu vielfach verfügbaren, fertigen Frameworks ist diese Variante auf den ersten Blick aufwendiger. Bei genauerer Betrachtung bieten sich jedoch einige Vorteile:
Information hiding: Das XML-Dokument kann direkt aus der Datenbank abgerufen werden. Der Nutzer greift auf eine View zu - die Herkunft der Daten bleibt verborgen.
Offenheit: Die so erzeugte XML-Schnittstelle ist für viele Programmierumgebungen (Java, .NET, PHP, Perl, C/C++ ...) nutzbar. Die einzige Voraussetzung ist die Fähigkeit zur Kommunikation mit der Datenbank.
Performance: Besonders das Erzeugen von XML ist hier sehr performant gelöst. Denn die Datenbank stellt das XML-Dokument zusammen und liefert es „auf einmal“ aus. Dies ist wesentlich effizienter als das Ausliefern zahlreicher Einzelsätze.
Links und Literatur