XmlJdbc Treiber

Einleitung

Der XmlJdbc Treiber erlaubt es, XML-Dateien über Jdbc zu lesen. Mit Hilfe von SQL-Abfragen können damit Daten aus einer XML-Datei genauso wie aus einer Datenbanktabelle extrahiert und weiterverarbeitet werden.

Zur Abfrage werden Elemente in einem XML-Dokument als Tabellen angesprochen. Dazu verwendet man drei Komponenten in der Abfrage:

  • als Datenbank wird eine Dummyverbindung 'jdbc:xml:' verwendet
  • das Schema ist eine Json-Datei (in der FROM Klausel)
  • die Tabelle ist ein XPath-Ausdruck (in der FROM Klausel), der zu einem oder mehreren XML-Elementen führt, die als Zeilen behandelt werden
  • die Spalten sind Attribute (@attributeName) oder Child-Elemente (elementName) innerhalb des Zeilen-Elements

Das folgende XML-Dokument kann man zum Beispiel mit Hilfe des Treibers einlesen:

<?xml version="1.0" encoding="UTF-8"?>
<people>
    <person id="10" profession="scientist">
        <fname>Henry</fname>
        <lname>Cavendish</lname>
    </person>
    <person id="21" profession="scientist">
        <fname>Isaac</fname>
        <lname>Newton</lname>
    </person>
    <person id="33" profession="scientist">
        <fname>Doctor</fname>
        <lname>Seltsam</lname>
    </person>
</people>

Eine SQL-Abfrage über den XmlJdbc Treiber sieht dann so aus:

SELECT "@id"
    , fname
    , lname
    , "@profession"
FROM "xmljdbc/src/test/resources/de/softquadrat/jdbc/xml/people.xml"."/people/person"
COLUMNS("@id", fname, lname, "@profession")

Dabei wird der Dateiname "xmljdbc/src/test/resources/de/softquadrat/jdbc/xml/people.xml" als Schema angegeben und "/people/person" ist ein Pfad-Ausdruck, der zu den "person" Elementen im Dokument führt.

Innerhalb der "person" Elemente werden mit "@id" und "@profession" die Attribute und mit "fname" und "lname" die Child-Elemente angesprochen.

Das Ergebnis sieht dann so aus:

@id fname  lname     @profession
--- ------ --------- -----------
10  Henry  Cavendish scientist
21  Isaac  Newton    scientist
33  Doctor Seltsam   scientist

Aliase

Wie gewohnt können Spaltenaliase vergeben werden, um die Spaltennamen umzubenennen:

SELECT "@id" AS id
    , fname AS vorname
    , lname AS nachname
    , "@profession" AS beruf
FROM "xmljdbc/src/test/resources/de/softquadrat/jdbc/xml/people.xml"."/people/person"
COLUMNS("@id", fname, lname, "@profession")

Das liefert dann:

id vorname nachname beruf
-- ------- --------- ---------
10 Henry   Cavendish scientist
21 Isaac   Newton    scientist
33 Doctor  Seltsam   scientist

Pseudo-Spalten

Neben den Datenspalten aus dem XML-Dokument generiert der XmlJdbc Treiber zusätzliche Pseudospalten:

  • rownumber
  • $id
  • $parentid
SELECT rownumber AS rownum
    , "$id" AS zeilen_id
    , "@id" AS id
    , fname
    , lname
    , "@profession"
FROM "xmljdbc/src/test/resources/de/softquadrat/jdbc/xml/people.xml"."/people/person"
COLUMNS("@id", fname, lname, "@profession")

Die Pseudospalte "rownumber" liefert eine fortlaufende Nummer für jeden Datensatz des Ergebnisses. Die Pseudospalte "$id" liefert eine fortlaufende Nummer für das Element im Dokument. Das Ergebnis der Beispielabfrage sieht so aus:

rownum zeilen_id id fname  lname     @profession
------ --------- -- ------ --------- -----------
1      0         10 Henry  Cavendish scientist
2      1         21 Isaac  Newton    scientist
3      2         33 Doctor Seltsam   scientist

Hinweis: Es gibt weiterhin noch die Pseudospalte "$parentid". Sie liefert den Pfad des darüberliegenden Elements, dazu mehr im nächsten Abschnitt.

Verschachtelte Elemente und Hierarchien

Ein Element in einem XML-Dokument kann weitere Sub-Elemente enthalten wie in dem folgenden Beispiel:

<?xml version="1.0" encoding="UTF-8"?>
<people>
    <person id="10" profession="scientist">
        <fname>Henry</fname>
        <lname>Cavendish</lname>
        <research>
            <area>Chemistry</area>
        </research>
        <research>
            <area>Density of the Earth</area>
        </research>
        <research>
            <area>Electricity</area>
        </research>
    </person>
    <person id="21" profession="scientist">
        <fname>Isaac</fname>
        <lname>Newton</lname>
        <research>
            <area>Calculus</area>
        </research>
        <research>
            <area>Optics</area>
        </research>
        <research>
            <area>Gravity</area>
        </research>
    </person>
    <person id="33" profession="scientist">
        <fname>Doctor</fname>
        <lname>Seltsam</lname>
    </person>
</people>

Verschachtelte Elemente

Verschachtelte Elemente können auf zwei Arten gelesen werden:

Option 1. Vollständiger Pfad

Alle Elemente können direkt über den vollständigen Pfad adressiert werden:

SELECT area
FROM "xmljdbc/src/test/resources/de/softquadrat/jdbc/xml/researcher.xml"."/people/person/research"
COLUMNS(area)

Die Abfrage liefert alle "research" Elemente:

area
--------------------
Chemistry
Density of the Earth
Electricity
Calculus
Optics
Gravity

Das ist das einfachste Verfahren, allerdings fehlt bei diesem Ansatz der Bezug zu den übergeordneten Elementen.

Option 2. Komma-separierte Hierarchie-Navigation

Der XmlJdbc Treiber unterstützt auch komma-separierte Navigation, ähnlich wie der JsonJdbc oder der YamlJdbc Treiber:

SELECT "$parentid", area
FROM "xmljdbc/src/test/resources/de/softquadrat/jdbc/xml/researcher.xml"."/people/person,research"
COLUMNS(area)

Hier navigiert "/people/person" zum ersten Hierarchie-Level und "research" (ohne führenden Slash!) zum zweiten Level relativ zu den gefundenen "person" Elementen.

Das liefert:

$parentid area
--------- --------------------
0         Chemistry
0         Density of the Earth
0         Electricity
1         Calculus
1         Optics
1         Gravity

Die "$parentid" zeigt, zu welchem "person" Element die "research" Elemente gehören (0 = erstes person Element, 1 = zweites person Element, usw.).

Korrelation mit Eltern-Elementen

Die Pseudospalte "$parentid" ermöglicht die Korrelation zwischen verschachtelten Elementen und ihren Eltern-Elementen.

Best Practice für verschachtelte Hierarchien:

  1. Erste Abfrage - Lesen Sie die Eltern-Elemente und speichern Sie die "$id" Pseudospalte als Primärschlüssel:
    SELECT "$id", "@id" AS person_id, fname, lname
    FROM "researcher.xml"."/people/person"
    COLUMNS("@id", fname, lname)

Das liefert:

$id person_id fname  lname
--- --------- ------ --------
0   10        Henry  Cavendish
1   21        Isaac  Newton
2   33        Doctor Seltsam
  1. Zweite Abfrage - Lesen Sie die Kind-Elemente mit komma-separierter Navigation:
    SELECT "$parentid", area
    FROM "researcher.xml"."/people/person,research"
    COLUMNS(area)

Das liefert:

$parentid area
--------- --------------------
0         Chemistry
0         Density of the Earth
0         Electricity
1         Calculus
1         Optics
1         Gravity
  1. Verknüpfung - Die "$parentid" aus der zweiten Abfrage entspricht der "$id" aus der ersten Abfrage, so können Sie die Datensätze in Ihrer Datenbank korrekt verknüpfen.

Hinweis zu relativen Pfaden in Hierarchien

Bei komma-separierter Navigation:

  • Der erste Pfad beginnt mit "/" und ist absolut vom Root-Element
  • Weitere Pfade sind relativ zu den gefundenen Elementen der vorherigen Ebene und beginnen ohne "/"

Beispiel:

-- Korrekt:
FROM "file.xml"."/root/element,subelement,subsubelement"
                  ↑ absolut      ↑ relativ  ↑ relativ

-- Falsch:
FROM "file.xml"."/root/element,/subelement,/subsubelement"
                                ↑ nicht relativ!

Attribute und Elemente

Der XmlJdbc Treiber unterscheidet zwischen XML-Attributen und XML-Elementen:

  • Attribute werden mit vorangestelltem "@" referenziert: @attributeName
  • Child-Elemente werden direkt mit ihrem Namen referenziert: elementName
  • Attribute von Child-Elementen werden mit elementName/@attributeName referenziert

Beispiel mit gemischten Attributen und Elementen:

<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book id="1" category="fiction">
        <title>The Great Gatsby</title>
        <author>F. Scott Fitzgerald</author>
        <year>1925</year>
        <price currency="EUR">10.99</price>
    </book>
    <book id="2" category="programming">
        <title>Clean Code</title>
        <author>Robert C. Martin</author>
        <year>2008</year>
        <price currency="USD">45.00</price>
    </book>
</books>
SELECT "@id" AS id
    , "@category" AS kategorie
    , title
    , author
    , year
    , price
    , "price/@currency" AS waehrung
FROM "books.xml"."/books/book"
COLUMNS("@id", "@category", title, author, year, price, "price/@currency")

Das liefert:

id kategorie    title            author               year price waehrung
-- ------------ ---------------- -------------------- ---- ----- --------
1  fiction      The Great Gatsby F. Scott Fitzgerald 1925 10.99 EUR
2  programming  Clean Code       Robert C. Martin    2008 45.00 USD

Systemtabellen

Der XmlJdbc Treiber unterstützt Verzeichnisse als Systemtabellen. Das erlaubt in einer Loop-Query eines datasqill Moduls nach Dateien in einem Verzeichnis zu suchen, die dann anschließend geladen werden können.

Die folgende Abfrage benutzt die Funktion "files()" als Systemtabelle und ihre Spalten "directory" und "filename".

SELECT directory, filename
    FROM "xmljdbc/src/test/resources/de/softquadrat/jdbc/xml".files()
    WHERE filename = 'books.xml' OR filename = 'employees.xml'
    ORDER BY filename

Als Ergebnis werden die gesuchten Dateien in dem verwendeten Verzeichnis zurückgeliefert:

directory                                          filename
-------------------------------------------------- -------------
xmljdbc/src/test/resources/de/softquadrat/jdbc/xml books.xml
xmljdbc/src/test/resources/de/softquadrat/jdbc/xml employees.xml

Anmerkungen

Datentypen

Der XmlJdbc Treiber liefert alle Ergebnisse als Text zurück. Eine Umwandlung in andere Datentypen muss in nachgelagerten Arbeitsschritten erfolgen.

Leeres Ergebnis

Sollte eine Abfrage keine Datensätze zurückgeben, so ist zu prüfen, ob der Pfad-Ausdruck in der FROM-Klausel tatsächlich zu XML-Elementen navigiert. Ist dies nicht der Fall, ignoriert der XmlJdbc Treiber die Abfrage und gibt keine Ergebnisse zurück.

Null-Werte

Der XmlJdbc Treiber liefert für selektierte Spalten in der SELECT-Klausel deren Inhalt zurück. Ist das angesprochene Attribut oder Element nicht vorhanden, wird ein Null-Wert zurückgegeben.

<test>
    <item id="1">
        <name>Test1</name>
    </item>
    <item id="2">
        <!-- name fehlt -->
    </item>
</test>
SELECT "@id"
    , name
    , description
FROM "test.xml"."/test/item"
COLUMNS("@id", name, description)
@id name  description
--- ----- -----------
1   Test1 null
2   null  null

Lesen von Element-Inhalten

Ist das Element in der SELECT-Klausel nicht leer, wird dessen Textinhalt als Ergebnis zurückgegeben. Hat ein Element sowohl Textinhalt als auch Child-Elemente, wird nur der direkte Textinhalt (ohne die Child-Elemente) zurückgegeben.

<test>
    <item id="1">
        <name>Test1</name>
        <details>
            <created>17.3.2022</created>
            <description>only a test</description>
        </details>
    </item>
    <item id="2">
        <name>Test2</name>
        <tags>
            <tag>important</tag>
            <tag>urgent</tag>
        </tags>
    </item>
</test>
SELECT "@id"
    , name
FROM "test.xml"."/test/item"
COLUMNS("@id", name)
@id name
--- -----
1   Test1
2   Test2

XPath-Einschränkungen

Der XmlJdbc Treiber verwendet einen vereinfachten XPath-Syntax für die Navigation im XML-Dokument. Vollständige XPath-Ausdrücke mit Prädikaten, Achsen oder Funktionen werden nicht unterstützt.

Unterstützt werden:

  • Einfache Pfade wie /root/element/subelement
  • Attribut-Referenzen mit @attributeName
  • Child-Element-Referenzen mit elementName
  • Attribute von Child-Elementen mit elementName/@attributeName

Nicht unterstützt werden:

  • XPath-Prädikate wie /root/element[@id='1']
  • XPath-Achsen wie //element oder parent::*
  • XPath-Funktionen wie count() oder text()

Hybride Verarbeitung: StAX und DOM

Der XmlJdbc Treiber verwendet eine hybride Architektur für optimale Performance und Flexibilität:

StAX für einfache Pfade

Bei einfachen, nicht-hierarchischen Abfragen (ohne Komma-Separierung) wird StAX (Streaming API for XML) verwendet:

-- Verwendet StAX
FROM "file.xml"."/people/person"

Vorteile:

  • Sehr speichereffizient: Das XML-Dokument muss nicht vollständig in den Speicher geladen werden
  • Performant: Sequentielles Lesen ohne DOM-Aufbau
  • Geeignet für sehr große Dateien: Auch XML-Dateien mit mehreren GB können verarbeitet werden

DOM für hierarchische Pfade

Bei hierarchischen Abfragen mit komma-separierter Navigation wird DOM (Document Object Model) verwendet:

-- Verwendet DOM
FROM "file.xml"."/people/person,research"

Hinweis:

  • Das XML-Dokument wird vollständig in den Speicher geladen
  • Für sehr große XML-Dateien mit tiefen Hierarchien kann dies speicherintensiv sein
  • Dafür ist die Navigation durch verschachtelte Strukturen einfacher und flexibler

Praktische Richtlinien für Dokumentgrößen:

  • < 100 MB: DOM ist völlig in Ordnung, kein Performance-Problem
  • 100 MB - 500 MB: DOM funktioniert meist noch gut, achten Sie auf verfügbaren Heap-Speicher
  • 500 MB - 1 GB: DOM wird langsam kritisch, bevorzugen Sie StAX wenn möglich
  • > 1 GB: StAX ist dringend empfohlen für einfache Pfade; verwenden Sie DOM nur wenn hierarchische Navigation unbedingt nötig ist

Empfehlung: Verwenden Sie einfache Pfade ohne Komma-Separierung wann immer möglich, um von der Speichereffizienz von StAX zu profitieren. Für die meisten XML-Dateien in der Praxis (typisch < 100 MB) spielt der Unterschied jedoch keine große Rolle.

Zeichensätze

Der XmlJdbc Treiber erkennt automatisch die Zeichensatz-Deklaration im XML-Header (<?xml version="1.0" encoding="UTF-8"?>). UTF-8 ist der Standard-Zeichensatz, wenn keine explizite Deklaration vorhanden ist.

Namespaces

Aktuell unterstützt der XmlJdbc Treiber keine XML-Namespaces. Elemente mit Namespace-Präfixen werden als reguläre Elementnamen behandelt.

Zusammenfassung

Der YamlJdbc Treiber bietet eine einfache und intuitive Möglichkeit, XML-Dateien über SQL-Abfragen zu lesen:

  • ✅ Einfache Punkt-Notation für Navigation
  • ✅ Komma-separierte Hierarchie-Navigation wie bei JSON und YAML
  • ✅ Systemtabellen für Datei-Listings
  • ✅ Pseudo-Spalten für Hierarchie-Informationen
  • ✅ Vollständig kompatibel mit datasqill-Modulen
  • ✅ Gleiche Syntax-Patterns wie JsonJdbc und YamlJdbc