XML-Instanzen mit SQL/XML erzeugen

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

Auch die Erweiterungen, die wir hier besprechen wollen, sind Teil des ISO-SQL-Standards, und zwar der Spezifikation SQL/XML. Im Kern handelt es sich um einfache Zeilen- und einige wenige Gruppenfunktionen, die wie alle anderen Zeilen- und Gruppenfunktionen in SQL verwendet werden. Den hier geschilderten Funktionen ist gemein, dass Sie einen Datentyp erzeugen, der mit der Oracle-Datenbank mitgeliefert wird und XMLType heißt. Dieser Datentyp ist nicht einfach nur ein Texttyp, der aussieht wie XML, sondern ein sogenannter objektorientierter Typ, der einerseits die Daten enthält, andererseits aber auch Methoden implementiert, um mit den Daten zu arbeiten. Im Fall des Typs XMLType sind dies Funktionen zum Suchen in XML mit Hilfe des Standards XPath, zum Umwandeln von XML mit Hilfe von XSLT-Dateien und weitere Aufgaben.

Ein einfaches Beispiel

Ich möchte Ihnen gern Schritt für Schritt die Erzeugung einer XML-Instanz zeigen. Die Aufgabenstellung: Wir erstellen eine XML-Instanz für jede Abteilung des Unternehmens, die die Daten der Mitarbeiter dieser Abteilung enthält. Bevor wir uns dieser Aufgabe nähern, sollten Sie verstehen, dass die Funktionen aus dem SQL/XML-Standard, die wir hierzu verwenden werden, ganz normale Zeilen- und Gruppenfunktionen sind. Sie entsprechen syntaktisch also einer Funktion lower oder sum und können ebenso verwendet werden, sogar in Kombination mit den »normalen« Zeilenfunktionen.

Beginnen wir mit einer zunächst seltsam aussehenden Anweisung:

select xmlelement("Mitarbeiter") resultat
    from dual;


RESULTAT
----------------------------------------------
<Mitarbeiter></Mitarbeiter>

Code-Beispiel: Start – Eine etwas seltsame Anweisung

Die Anweisung nutzt die Zeilenfunktion xmlelement und übergibt einen Parameter in doppelten Anführungszeichen. Die doppelten Anführungszeichen sind hier Pflicht, weil XML Groß- und Kleinschreibung unterscheidet und wir sicherstellen möchten, dass die Elementnamen exakt so benannt werden, wie wir das vorgeben. Das Ergebnis erzeugt ein leeres XML-Element. Bereits dieses Ergebnis ist für die Datenbank nun kein Text mehr, sondern tatsächlich XML.

Die nächste Erweiterung erzeugt zunächst einige Attribute in diesem Wurzelelement, daher wechsele ich nun zur Tabelle emp. Wir möchten das Einstellungsdatum und die Mitarbeiternummer als Attribute modellieren. Um dies zu erreichen, schachteln Sie in den Aufruf der Funktion xmlelement als zweiten Parameter eine Funktion mit dem Namen xmlattributes. Diese Funktion erlaubt es Ihnen, eine Liste von Spalten mit einem Alias zu übergeben. Das Spaltenalias wird der Name des Attributs, der Spaltenwert der Attributwert. Beachten Sie bereits jetzt, dass wir eine sehr saubere Formatierung benötigen, damit wir nicht den Überblick über die ganzen Klammern verlieren. Nichts ist schlimmer, als nach einer seitenlangen Anweisung die Fehlermeldung ORA-00907: Rechte Klammer fehlt zu erhalten. Seien Sie also akribisch bei der Formatierung, dann sollte nichts schiefgehen.

select xmlelement("Mitarbeiter",
     xmlattributes(
        hiredate "einstellDatum",
        empno "id"
       )
    ) resultat
from emp;


RESULTAT
----------------------------------------------------------------
<Mitarbeiter einstellDatum="1980-12-17" id="7369"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-02-20" id="7499"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-02-22" id="7521"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-04-02" id="7566"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-09-28" id="7654"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-05-01" id="7698"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-06-09" id="7782"></Mitarbeiter>
<Mitarbeiter einstellDatum="1987-04-19" id="7788"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-11-17" id="7839"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-09-08" id="7844"></Mitarbeiter>
<Mitarbeiter einstellDatum="1987-05-23" id="7876"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-12-03" id="7900"></Mitarbeiter>
<Mitarbeiter einstellDatum="1981-12-03" id="7902"></Mitarbeiter>
<Mitarbeiter einstellDatum="1982-01-23" id="7934"></Mitarbeiter>

14 Zeilen ausgewählt.

Code-Beispiel: Erweiterung um Attribute

Durch diesen Schritt hat Oracle also die Attribute angelegt. Attribute werden stets im direkt übergeordneten XML-Element eingefügt. Im nächsten Schritt erzeuge ich nun Kindelemente. Da diese Kindelemente bereits Blätter meiner XML-Struktur darstellen und keine Attribute enthalten sollen (ihr Inhalt ist also vom Typ simpleType), können wir hier eine Funktion mit dem schönen Namen xmlforest verwenden, die, analog zur Funktion xmlattributes, Kindelemente durch Übergabe von Spalten und Spaltenaliasen erzeugt. Sollen Ihre Kindelemente wiederum Kindelemente oder Attribute enthalten, funktioniert dieser Weg nicht, sie können aber auch die Funktion xmlelement aufrufen und haben dann wieder alle Möglichkeiten. Ich werde diesen Weg mit der nächsten Iteration beschreiten. Die Verwendung der nächsten Funktion hat noch die Besonderheit, dass ich einige Zeilenfunktionen aus dem bereits bekannten Umfeld verwendet habe, um zu zeigen, dass die Schachtelung von Zeilenfunktionen natürlich auch in diesem Umfeld funktioniert:

select xmlelement("Mitarbeiter",
          xmlattributes(empno "id",
             hiredate "einstellDatum"),
          xmlforest(initcap(ename) "Name",
             initcap(job) "Beruf",
             to_char(sal, '9G990D00L') "Gehalt
         )
       )   resultat
    from emp;


RESULTAT
----------------------------------------------------------------
<Mitarbeiter id="7369" einstellDatum="1980-12-17"><Name>Smith</N
<Mitarbeiter id="7499" einstellDatum="1981-02-20"><Name>Allen</N
<Mitarbeiter id="7521" einstellDatum="1981-02-22"><Name>Ward</Na
<Mitarbeiter id="7566" einstellDatum="1981-04-02"><Name>Jones</N
<Mitarbeiter id="7654" einstellDatum="1981-09-28"><Name>Martin</
<Mitarbeiter id="7698" einstellDatum="1981-05-01"><Name>Blake</N
<Mitarbeiter id="7782" einstellDatum="1981-06-09"><Name>Clark</N
<Mitarbeiter id="7788" einstellDatum="1987-04-19"><Name>Scott</N
<Mitarbeiter id="7839" einstellDatum="1981-11-17"><Name>King</Na
<Mitarbeiter id="7844" einstellDatum="1981-09-08"><Name>Turner</
<Mitarbeiter id="7876" einstellDatum="1987-05-23"><Name>Adams</N
<Mitarbeiter id="7900" einstellDatum="1981-12-03"><Name>James</N
<Mitarbeiter id="7902" einstellDatum="1981-12-03"><Name>Ford</Na
<Mitarbeiter id="7934" einstellDatum="1982-01-23"><Name>Miller</

14 Zeilen ausgewählt.

Eine einzelne Zeile sieht komplett (und etwas formatiert) so aus:

<Mitarbeiter id="7369" einstellDatum="1980-12-17">
    <Name>Smith</Name>
    <Beruf>Clerk</Beruf>
    <Gehalt>800,00€</Gehalt>
</Mitarbeiter>

Code-Beispiel: Eine verfeinerte Version mit Kindelementen

Es scheint Oracle durchaus bekannt zu sein, auf welche Weise ein Datum XML-konform zu konvertieren ist, denn die Übergabe der Spalte HIREDATE erfolgt als date, ohne Formatierungsmaske. Das Standarddatumsformat von XML lautet yyyy-mm-ddThh24:mi:ss. Da es sich bei der Spalte HIREDATE aber um ein Datum handelt und nicht um einen Zeitstempel, ist hier die kürzere und ebenfalls in XML erlaubte Form yyyy-mm-dd gewählt worden, denn für ISO ist ein Datum stets das Datum ohne Uhrzeit, wie Sie sich sicher erinnern. Möchten Sie aus einem Datum die Uhrzeit nach XML »retten«, müssen Sie die Konvertierung explizit vornehmen, oder aber die date-Spalte in eine timestamp-Spalte konvertieren. Bevor wir uns weitere Möglichkeiten ansehen, möchte ich diesen Punkt gern etwas verdeutlichen: Wie funktioniert die automatische Formatumwandlung nun genau? Sehen wir uns hierzu zwei Beispielabfragen an, die Ihnen ein Gefühl hierfür vermitteln werden:

select xmlelement("Test", current_timestamp) ts
   from dual;


TS
-------------------------------------------------
<Test>2012-03-28T13:34:37.312000+02:00</Test>


select xmlelement("Test", current_date) dt
   from dual;


DT
-------------------------------------------------
<Test>2012-03-28</Test>

Code-Beispiel: Automatische Umformung eines Datums

Nun ist es klarer: Ein Datum wird – ISO-konform – in eine kurze, ein Zeitstempel in eine lange Datumsangabe in XML konvertiert.

Aber weiter im Text. Da wir diese Funktionen als Zeilenfunktionen für eine Tabelle aufrufen, ist es nicht verwunderlich, dass wir eine entsprechende Anzahl an Zeilen zurückgeliefert bekommen. Jede enthält nun eine gültige Instanz des Typs XMLType und könnte somit jede der für diesen Typ definierten Typfunktionen aufrufen. Unsere Aufgabestellung war jedoch eine andere: Wir wollten eine Mitarbeiterliste pro Abteilung mit allen Mitarbeitern der Abteilung erstellen. Wir benötigen also noch eine Gruppenfunktion.

Zunächst einmal erweitern wir unsere Ausgabe durch ein weiteres Element, das den Namen Mitarbeiterliste bekommen soll:

select xmlelement("Mitarbeiterliste",
           xmlelement("Mitarbeiter",
             xmlattributes(empno "id",
                hiredate "einstellDatum"),
             xmlforest(initcap(ename) "Name",
             initcap(job) "Beruf",
             to_char(sal, '999G990D00L') "Gehalt")
        )
     ) resultat
from emp;

Hier sehen Sie ein Beispiel dafür, dass die Funktion xmlelement als Parameter in die Funktion xmlelement eingeschachtelt werden kann. Diese Schachtelung hat noch keine Gruppierung zur Folge, es wird lediglich ein weiteres XML-Element um jede Zeile gelegt. Nun folgt der entscheidende Schritt:

select xmlelement("Mitarbeiterliste", 
        xmlagg( 
          xmlelement("Mitarbeiter", 
            xmlattributes(empno "id", 
                hiredate "einstellDatum"), 
            xmlforest(initcap(ename) "Name", 
                      initcap(job) "Beruf", 
                      to_char(sal, '999G990D00L')  "Gehalt") 
           )    
        ) 
     )  resultat 
   from emp;


RESULTAT
---------------------------------------------------------------
<Mitarbeiterliste><Mitarbeiter id="7369" einstellDatum="1980-12

Code-Beispiel: Gruppierung der XML-Ausgabe in einer Zeile

Durch die Verwendung der Gruppenfunktion xmlagg werden die innerhalb dieser Funktion liegenden Ergebnisse zu einer Gruppe von XML-Elementen (einem XML-Fragment ) zusammengefasst und in das umgebende Element eingeschachtelt. Nun haben wir nur noch eine Zeile, nicht aber, wie eigentlich gefordert, eine Mitarbeiterliste pro Abteilung. Diese letzte Umwandlung hat jetzt aber nichts mehr mit SQL/XML, sondern mit einfachem SQL zu tun, denn die Gruppenfunktion wird über die Klausel group by lediglich partitioniert. Um das Ergebnis zu verdeutlichen, werde ich zusätzlich noch die Abteilungsnummer als Attribut abteilung im äußeren XML-Element Mitarbeiterliste ausgeben:

select xmlelement("Mitarbeiterliste",
          xmlattributes(
          deptno "abteilung"
        ),
            xmlagg(
              xmlelement("Mitarbeiter",
                 xmlattributes(
                    empno "id",
                    hiredate "einstellDatum"),   
                xmlforest(
                   initcap (ename) "Name",
                   initcap(job) "Beruf",
                   trim(to_char(sal, '999G990D00L')) "Gehalt"
                )
            )
         )
      ) resultat
    from emp
group by deptno;


RESULTAT
----------------------------------------------------------------
<Mitarbeiterliste abteilung="10"><Mitarbeiter id="7782" einstell
<Mitarbeiterliste abteilung="20"><Mitarbeiter id="7369" einstell
<Mitarbeiterliste abteilung="30"><Mitarbeiter id="7499" einstell

Eine einzelne Zeile sieht formatiert nun aus wie folgt:

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

Code-Beispiel: Das Resultat – Eine Mitarbeiterliste pro Abteilung in XML

Die Syntax ist zunächst etwas gewöhnungsbedürftig, doch stimmen Sie mir sicher zu, dass die Erzeugung von XML aus relationalen Daten mit Hilfe dieser Funktionen äußerst einfach zu bewerkstelligen ist. Immerhin benötigen wir keinerlei Programmierung, eine einfache SQL-Anweisung genügt. Nach diesem Prinzip lassen sich zudem beliebig komplex geschachtelte XML-Instanzen erzeugen. Das Einzige, was Sie immer behalten sollten, ist: den Überblick. Werden Ihre Abfragen umfangreicher, ist die Kontrolle über die Funktionsaufrufe, deren Attribute und Schachtelung und insbesondere über die ganzen Klammerebenen überlebenswichtig.

Sie haben zudem die Möglichkeit, über skalare oder harmonisierte Unterabfragen eine tiefer geschachtelte XML-Struktur zu erzeugen. Die SQL-Anweisung für solche Strukturen ist dann zwar nicht mehr trivial, doch kann ich andererseits aus meiner Erfahrung mit XML-Strukturen in Datenbanken auch sagen, dass es eher selten ist, dass komplex strukturierte XML-Instanzen aus relationalen Daten erzeugt werden müssen. Die meisten Nachrichten kommen mit relativ schlichten inneren Strukturen aus. Eine andere Limitierung betrifft die Größe der resultierenden XML-Instanz. Zwar ist diese Größe nicht grundsätzlich limitiert, da XMLType letztlich ein clob ist, doch steigt die erforderliche Rechenzeit und auch der Speicherverbrauch exponentiell an. Über den Grund hierfür muss ich spekulieren, ich gehe jedoch davon aus, dass diese Funktionen einen DOM-Baum im Arbeitsspeicher einrichten und die Ergebnisse der Funktion als neue Elemente des DOM-Baums berechnen und dort einfügen. Da ein DOM-Baum im Arbeitsspeicher leicht die zehnfache Größe der resultierenden XML-Datei erreichen kann, wird verständlich, dass der Aufwand, eine solche Struktur zu verwalten, immer größer wird.

Exkurs DOM und SAX
Ich habe den Begriff DOM-Baum verwendet. Hierzu eine kurze Erläuterung: Eine XML-Instanz ist hierarchisch organisiert, hat also intern eine Baumstruktur. Allerdings ist das, was Sie und ich sehen, wenn wir eine XML-Instanz anschauen, nicht alles, was im Zusammenhang mit DOM unter einem Baum verstanden wird. Zunächst einmal (ich habe vergessen, ihn vorzustellen) bedeutet DOM Document Object Model. Damit wird eine Darstellung einer beliebigen XML-Struktur verstanden. Wir unterscheiden die Wurzel, das Wurzelelement, Knoten und Blätter, aber auch Attribute, Weißraum, Elementwerte und andere Arten von Elementen. DOM-Bäume beschreiben diese ganzen Elemente in epischer Breite im Arbeitsspeicher und dienen dazu, einen SQL-Parser, also ein Programm, das XML verarbeiten soll, die Navigation und Manipulation der XML-Instanz zu ermöglichen.
Im Gegensatz dazu hört man verschiedentlich den Begriff SAX im Zusammenhang mit XML. Darunter versteht man ein dynamischeres Modell einer XML-Instanz. Eigentlich ist es sogar gar kein Modell, sondern eine Programmierstrategie: Eine XML-Instanz wird nämlich bei SAX seriell durchgelesen, vom Anfang bis zum Ende. Das eigentliche Programm »hört« hierbei zu und mischt sich ein, wenn etwas Interessantes kommt. Der Vorteil von SAX besteht darin, dass nicht der komplette Baum im Arbeitsspeicher aufgebaut werden muss, sondern immer nur der aktuell gelesene Knoten. Daher ist die Verarbeitung vieler XML-bezogener Prozesse mit Mitteln der SAX-Programmierung leichtgewichtiger und ressourcenschonender als der DOM-Ansatz, der dafür aber besser geeignet ist, wenn wir im Baum hin- und herspringen möchten.

In der Praxis tendiere ich dazu, mit dieser Technik XML-Instanzen bis zu einer Größe von ca. 1 MB zu erzeugen. Darüber hinaus sind andere Verfahren effizienter. Insbesondere bietet sich hier die Erzeugung einer XML-Instanz über die objektorientierten Funktionen der Datenbank an, denn es scheint, als würde die XML-Datei hier »in einem Rutsch« aus den objektorientierten Strukturen erzeugt und nicht Knoten für Knoten über den DOM-Baum. Daher ist dieser Weg zwar deutlich komplizierter zu erstellen und auch deutlich weniger flexibel, bei entsprechenden Anforderungen an die Performanz aber auch deutlich leistungsfähiger. Zudem beschreite ich diesen Weg, wenn ich in kurzer Zeit sehr viele XML-Instanzen erzeugen muss. Denn dann fallen die vielen einzelnen Erzeugungsschritte negativ ins Gewicht. Vielleicht formuliere ich es einmal so: Ich verwende den Ansatz von oben, solange es von der Performanz her in Ordnung ist, und ich wechsle zum objektorientierten Ansatz, wenn ich muss.

Übersicht über weitere SQL/XML-Funktionen

Neben den bis jetzt besprochenen Funktionen existieren in der Oracle-Datenbank noch eine Reihe weiterer Funktionen im Umfeld von SQL/XML, die wir hier besprechen werden.

Weitere Funktionen zur Erzeugung von XML-Instanzen

Im Grunde sind die folgenden Funktionen größtenteils selbsterklärend, da sie dazu dienen, besondere sprachliche Elemente in XML, wie etwa Processing Instructions, Kommentare oder Prologinformationen, zu XML zu erzeugen. In diese Gruppe fallen die folgenden Funktionen:

  • xmlroot
    Erzeugung eines XML-Prologs der Form <?xml version="1.0" standalone="yes"?>. Die Syntax ist insofern ein wenig unangenehm, als diese Funktion die gesamte xmlelement-Funktion einschließt und erst danach die weiteren Parameter übergibt. Dadurch wird der Beginn der Funktion und die Parameter sehr weit auseinandergezogen:
    xmlroot(xmlelement(...), version '1.0 ', standalone yes)
  • xmlpi
    Erzeugt eine Processing Instruction, übersetzt vielleicht eine Prozessoranweisung. Diese Funktion kann an beliebigen Stellen innerhalb der SQL-Anweisung stehen. Die Syntax lautet:
    xmlpi(name "xml-stylesheet", 'type="text/xsl" href="sample.xslt"')
  • xmlcomment
    Fügt, wie der Name vermuten lässt, einen Kommentar in den XML-Baum ein. Die Syntax ist einfach: xmlcomment('...')
  • xmlconcat
    Diese Funktion erlaubt es, XML-Fragmente aneinanderzuhängen, ähnlich wie das auch die concat-Funktion in SQL tut. Und ähnlich wie diese Funktion habe ich auch xmlconcat noch nie benötigt
  • xmlcdata
    CDATA-Abschnitte sind Abschnitte, die durch den XML-Parser ignoriert werden, aber keine Kommentare sind. Sie werden verwendet, um zum Beispiel Binärdaten (in Base64-Kodierung) in die XML-Datei einzufügen. Die Syntax lautet: xmlcdata('...')
  • xmlserialize
    Diese Funktion erlaubt es, eine XML-Instanz (entweder ein wohlgeformtes Dokument oder ein XML-Fragment) zu erzeugen. Der Sinn dieser Funktion liegt darin, das in ihr, je nach Parameter eine abweichende Zeichensatzkodierung verwendet, Weißraum eingerückt und das Ergebnis zum Beispiel als blob ausgegeben werden kann. Eine Verwendung dieser Funktion habe ich im Skript zum Buch eingefügt.

Funktionen zur Extraktion von Daten aus XML-Instanzen

Der SQL/XML-Standard kennt einige Funktionen, um Daten aus XML-Instanzen zu extrahieren. Oracle unterstützt die folgenden Funktionen:

  • extractValue
    Diese Funktion ist Oracles Ersatz für die Funktion xmlcast des SQL/XML-Standards, die es bei Oracle nicht gibt. Diese Funktion erwartet eine XML-Instanz sowie einen XPath-Ausdruck, der den Knotenwert definiert, der extrahiert werden soll. Anschließend kann das Ergebnis dieser Konvertierung mit entsprechenden Funktionen (to_*, cast) in den Zieltyp überführt werden.
  • xmltable
    Diese mächtige Funktion erlaubt es, mit Hilfe eines XPath- oder XQuery-Ausdrucks eine XML-Instanz in eine relationale Tabelle zu verwandeln. Sie werden noch ein Beispiel für den Einsatz dieser Funktion sehen.

SQL/XML-Funktionen, die Oracle nicht oder nicht komplett unterstützt

Zusätzlich zu den Funktionen kennen Sie möglicherweise Funktionen, die Sie bei Oracle verwenden möchten, die aber nicht implementiert sind. Einige Funktionen kennen hier eine Alternative, andere werden derzeit noch gar nicht unterstützt. Es ist nicht hilfreich, Ihnen hier eine aktuelle Liste aufzuschreiben, denn diese Liste dürfte mit dem nächsten Datenbank-Release schon wieder nicht stimmen, zudem werden einige Dinge nicht korrekt sein, wenn Sie eine ältere Datenbankversion als die hier beschriebene verwenden. Daher möchte ich für solche Fragen der Kompatibilität mit dem Standard auf Anhang C der Oracle Database SQL Language Reference verweisen. Dort ist eine detaillierte Diskussion der Konformität zum SQL/XML-Standard für Ihre Datenbank hinterlegt.

  

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