XML-Importer

Der easydb XML-Importer ist ein Kommandozeilen-Tool zum Import von XML-Daten in relationale Datenbanken. Es gibt keine Verbindung und Relation zur easydb. Mit dem Kommandozeilen-Tool können ganz allgemein XML-Dateien in PostgreSQL-Datenbanken importiert werden.

Für einen Import müssen mindestens drei Dateien angelegt werden:

Import-Plan-Datei

1:  2:  3:  4:  5:  6:  7:  8:  9:  10:  11:  12:  13:  14:  15: 
<!-- datei: import_easydb_plan.xml -->
<plan name="import_test" xmlns="http://schema.programmfabrik.de/imexporter-plan/0.1">
    <build-steps>
        <step type="splitxml">
            <split-level>1</split-level>
            <num-tuples>10000</num-tuples>
        </step>
        <step type="xslt">
            <xsl-file>import_test.xsl</xsl-file>
        </step>
        <step type="databaseimport">
            <description-file>import_easydb.xml</description-file>
        </step>
    </build-steps>
</plan>

  • Der erste build-step teilt die Eingabe-XML-Dateien in kleinere Portionen auf, so dass die XSL-Transformation nicht zuviel Speicher braucht.
  • Im zweiten Schritt wird die XSL-Definition import_test.xsl angegeben, die aus dem Eingabe-XML das zu importierende XML vorbereitet.
  • Im dritten Schritt erfolgt dann der eigentliche Datenbank-Import. Hierfür wird die Definition import_easydb.xml verwendet.

Der Import-Plan wird mit dem folgenden Kommando ausgeführt:

/opt/easydb/server/bin/imexporter \
  -d "host=... port=... dbname=... user=... password=..." \
  -p import_easydb_plan.xml \
  -i import_test.xml

Bitte beachten Sie, dass der imexporter aktuell nur UTF-8-kodierte Dateien unterstützt.

Import-Definitions-Datei

Grundsätzlich geht der Importer nach folgendem Muster vor:

  • Es werden für alle im zu importierenden XML gefundenen Tabellen-Namen temporäre Tabellen angelegt.
  • Die temporären Tabellen werden mit den Spalten angelegt, wie sie in der easydb-Datenbank vorhanden sind. Nicht angelegt werden die Spalten, die im Element filter-columns-input angegeben sind. Zusätzlich angelegt werden alle Spalten aus dem Element extra-columns.
  • Die Daten aus dem XML werden danach unkonditional in die temporären Tabellen kopiert.
  • Für alle Zeilen in allen Tabellen wird ein INSERT bzw. UPDATE Statement zusammengebaut, welches dann auf der easydb-Datenbank ausgeführt wird. Dabei werden grundsätzlich alle Spalten 1:1 übernommen. Nicht kopiert werden alle Spalte, die durch die Elemente extra-columns implizit und filter-columns-input explizit ausgeschlossen sind.
  • Durch das 1:1 matching ist es Voraussetzung, dass jede Zeile eindeutig identifziert werden kann. Dies geschiet über die Angabe von unique-keys.
1:  2:  3:  4:  5:  6:  7:  8:  9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76: 
<?xml version="1.0" encoding="utf-8"?>
<import-definition  xmlns="http://schema.programmfabrik.de/import-definition/0.1">
    <actions>
<!-- <empty/> -->
        <insert />
        <delete />
        <update />
    </actions>
    <table name="bilder">
        <restrict-import>
            <column name="reference_id">
                <not-null/>
            </column>
        </restrict-import>
        <unique-keys>
            <unique-key>
                <column name="reference_id"/>
            </unique-key>
        </unique-keys>
        <filter-columns-output>
            <filter-column name="id"/>
        </filter-columns-output>
        <extra-columns>
            <extra-column name="kuenstler_reference_id" type="int4" />
        </extra-columns>
        <lookups>
            <lookup other-table="kuenstler">
                <other-column name="reference_id">
                    <input-column name="kuenstler_reference_id"/>
                </other-column>
                <set-column name="kuenstler_id">
                    <other-column name="id"/>
                </set-column>
            </lookup>
        </lookups>
    </table>
    <table name="bilder_revision">
        <filter-columns-output>
            <filter-column name="id"/>
        </filter-columns-output>
        <unique-keys>
            <unique-key>
                <column name="bilder_id"/>
                <column name="version_no"/>
            </unique-key>
        </unique-keys>
        <extra-columns>
            <extra-column name="bilder_reference_id" type="int4" />
        </extra-columns>
        <lookups>
            <lookup other-table="bilder">
                <other-column name="reference_id">
                    <input-column name="bilder_reference_id"/>
                </other-column>
                <set-column name="bilder_id">
                    <other-column name="id"/>
                </set-column>
            </lookup>
        </lookups>
    </table>
    <table name="kuenstler">
        <restrict-import>
            <column name="reference_id">
                <not-null/>
            </column>
        </restrict-import>
        <unique-keys>
            <unique-key>
                <column name="reference_id"/>
            </unique-key>
        </unique-keys>
        <filter-columns-output>
            <filter-column name="id"/>
        </filter-columns-output>
    </table>
</import-definition>

actions

empty Diese Action dient zum Testen der XSL und XML Syntax, es wird keine Aktion durchgeführt.
insert Neue Datensätze werden angelegt.
update Veränderte Datensätze, die bereits in der Datenbank vorhanden sind, werden aktualisiert.
delete Datensätze, die in der Datenbank enthalten sind, aber nicht im Import gefunden und nicht ignoriert werden, werden gelöscht.

table

In der Auflistung der Tabellen gibt es die folgenden steuernden Elemente:

restrict-import Wenn Zeilen diese Spalten nicht matchen, werden die Zeilen beim Import ignoriert, d.h. bei einem vollen Import nicht gelöscht.
unique-keys Damit der Importer feststellen kann, ob eine Zeile neu geschrieben werden muss, oder aktualisiert werden soll, sucht der Importer in der Datenbank die Zeile mit Hilfe dieses Unique-Keys heraus. Die Angabe eines Unique-Keys ist zwingend. Der Unique-Key muss in der Datenbank existieren.
filter-columns-output Normalerweise baut der Importer alle INSERT und UPDATE Statements komplett für alle Spalten der Zeile zusammen, d.h. Spalten die sich nicht im zu importierenden XML befinden, werden explizit auf null gesetzt, es sei denn sie sind hier in filter-columns-output aufgelistet. Als Attribut kann ein name direkt angegeben werden, oder Spalten mit regexp ausgewählt werden.
filter-columns-input Normalerweise baut der Importer für alle im XML gefundenen Spalten eine Spalte in der temporären Import-Tabelle. Diese Spalte wird nicht angelegt, wenn sie in filter-columns-input aufgelistet ist. Als Attribut kann ein name direkt angegeben werden, oder Spalten mit regexp ausgewählt werden.
include-columns-output (ab der nächsten imexporter-Version) Wenn nur einige Spalten in der Datenbank geschrieben werden sollen, kann diese Positivliste verwendet werden. Die Namen der verwendeten Spalten werden entweder direkt (Attribut name) oder einen regulären Ausdruck (Attribut regex) angeben.
include-columns-input (ab der nächsten imexporter-Version) Wenn mehr als die Hälfte der Tabellenspalten nicht in der Import-Tabelle verwendet werden sollen, bietet sich eine Positivliste der zu verwendenden Spalten an. Damit werden nur die über ihren kompletten Namen (Attribut name) oder einen regulären Ausdruck (Attribut regex) beschrieben Spalten verwendet.
extra-columns Wenn das XML Spalten enthält, die nicht in der Datenbank-Tabelle vorhanden sind (z.B. um eine Referenz-Matching durchzuführen), so teilen Sie dies dem Importer mit der Angabe von extra-columns mit. Hierbei ist die Angabe eines PostgreSQL-Datentyps erforderlich. Sollten Sie die Spalte für eine Lookup benutzen, muss er denselben Typ haben, wie die entsprechende Spalte in der Datenbank-Tabelle auf der der Lookup durchgeführt wird.
lookups Mit lookups können Sie in der Datenbank etwas nachschauen und einen Wert korrespondierend zu einem gefundenen Eintrag im Rahmen des Imports umschreiben. Das bietet sich an, wenn z.B. die zu importierenden Daten eigenen Referenz-IDs benutzen, die dann auf easydb-IDs umgeschrieben werden müssen. Das Matching erfolgt über das Attribut other-table in der Tabelle in der easydb-Datenbank. Dabei wird in der Spalte other-column nach dem Wert der Spalte input-column aus der zum Import stehende Tabelle gesucht. Ist ein Eintrag gefunden, wird mit set-column angegeben welche Spalte in der easydb-Tabelle gesetzt wird. Der Wert steht in dem gefundenen Eintrag in der Spalte other-column.

Derzeit können Sie keine Wiederholgruppen importieren, wenn diese nicht über einen eindeutigen Unique-Key verfügen (was in den meisten easydb-Wiederholgruppen der Fall ist). Sie benötigen in dem Fall, eine neue Spalte in der Wiederholgruppe, die einen Unique-Key herstellen kann. In unserem Beispiel ist das die Spalte version_no in der Tabelle bilder_revision.

Beispiel-Daten

Für unser Beispiel wird mit den folgendne Daten gearbeitet:

1:  2:  3:  4:  5:  6:  7:  8:  9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71: 
<?xml version="1.0" encoding="UTF-8" ?>
<bilder>
  <bild>
     <reference>123</reference>
     <name>Mein erstes Bild</name>
     <kuenstler>
         <name>Horst Schramm</name>
         <reference>144</reference>
     </kuenstler>
     <standorte>
         <standort>
             <name>Europa &gt; Deutschland &gt; Berlin</name>
             <path>
                 <name>Europa</name>
                 <name>Deutschland</name>
                 <name>Berlin</name>
             </path>
         </standort>
     </standorte>
     <versions>
         <version>
             <author>Tom Mueller</author>
             <comment>Initiale Version</comment>
             <date>2011-10-08 14:50</date>
         </version>
         <version>
             <author>Tom Mueller</author>
             <comment>Rechtschreibfehler korrigiert</comment>
             <date>2011-10-08 21:10</date>
         </version>
         <version>
             <author>Heike Mueller</author>
             <comment>Verberssungen im Detail.</comment>
             <date>2011-10-09 09:50</date>
         </version>
     </versions>
  </bild>
  <bild>
     <reference>124</reference>
     <name>Mein zweites Bild</name>
     <kuenstler>
         <name>Horst Müller</name>
         <reference>145</reference>
     </kuenstler>
     <versions>
         <version>
             <author>Tom Mueller</author>
             <comment>Initiale Version</comment>
             <date>2011-10-08 14:50</date>
         </version>
         <version>
             <author>Tom Mueller</author>
             <comment>Rechtschreibfehler korrigiert</comment>
             <date>2011-10-08 21:10</date>
         </version>
         <version>
             <author>Heike Mueller</author>
             <comment>Verberssungen im Detail.</comment>
             <date>2011-10-09 09:50</date>
         </version>
     </versions>
  </bild>
<!--  <bild>
     <reference>124</reference>
     <name>Mein drittes Bild</name>
     <kuenstler>
         <name>Horst Schramm</name>
         <reference>144</reference>
     </kuenstler>
  </bild>-->
</bilder>

XSL-Transformations-Datei

Das Format dieser Datei ist easydb unabhängig, es handelt sich hierbei um reines XSL.

Das Ziel der Transformation sind normalisierte Daten in einer relativen Strukur, d.h. es gibt Tabellen mit Spalten. Die Tabellen-Struktur ist jene, die in der easydb-Datenbank vorhanden sind. In diesem Schritt findet das eigentliche Mapping der Daten statt. Zum Aufbereiten der Daten sind ggfs. fundierte XSL-Kenntnisse erforderlich.

Für das hier besprochene Beispiel verwenden wir die folgende Datei:

1:  2:  3:  4:  5:  6:  7:  8:  9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61: 
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
                xmlns="http://schema.programmfabrik.de/imexport-data/0.1">
    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="bilder">
        <dump>
            <table tablename="bilder">
                <xsl:apply-templates select="bild"/>
            </table>
            <table tablename="kuenstler">
                <xsl:apply-templates select="bild/kuenstler"/>
            </table>
            <table tablename="bilder_revision">
                <xsl:apply-templates select="bild/versions/version"/>
            </table>
        </dump>
    </xsl:template>
    <xsl:template match="bild">
        <row>
            <col name="reference_id">
                <xsl:value-of select="reference/text()"/>
            </col>
            <col name="titel">
                <xsl:value-of select="name/text()"/>
            </col>
            <col name="kuenstler_reference_id">
                <xsl:value-of select="kuenstler/reference/text()"/>
            </col>
        </row>
    </xsl:template>
    <xsl:template match="bild/kuenstler">
        <row>
            <col name="reference_id">
                <xsl:value-of select="reference/text()"/>
            </col>
            <col name="name">
                <xsl:value-of select="name/text()"/>
            </col>
        </row>
    </xsl:template>
    <xsl:template match="bild/versions/version">
        <row>
            <col name="bilder_reference_id">
                <xsl:value-of select="../../reference/text()"/>
            </col>
            <col name="author">
                <xsl:value-of select="author/text()"/>
            </col>
            <col name="comment">
                <xsl:value-of select="comment/text()"/>
            </col>
            <col name="created">
                <xsl:value-of select="date/text()"/>
            </col>
            <col name="version_no">
                <xsl:value-of select="count(preceding-sibling::*)"/>
            </col>
        </row>
    </xsl:template>
</xsl:stylesheet>

XML-Daten für den Import

Die hier gezeigte XML-Datei wird aus den Beispiel-Daten und dem XSL erzeugt und dient als Input für den Datenbank-Import. Diese Datei können Sie mit XSLT-Tools wie z.B. xsltproc erzeugen:

xsltproc import_test.xsl import_test.xml > test.xml
1:  2:  3:  4:  5:  6:  7:  8:  9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63: 
<?xml version="1.0"?>
<dump xmlns="http://schema.programmfabrik.de/imexport-data/0.1">
  <table tablename="bilder">
    <row>
      <col name="reference_id">123</col>
      <col name="titel">Mein erstes Bild, wirklich, wirklich</col>
      <col name="kuenstler_reference_id">144</col>
    </row>
    <row>
      <col name="reference_id">124</col>
      <col name="titel">Mein zweites Bild</col>
      <col name="kuenstler_reference_id">145</col>
    </row>
  </table>
  <table tablename="kuenstler">
    <row>
      <col name="reference_id">144</col>
      <col name="name">Horst Schramm</col>
    </row>
    <row>
      <col name="reference_id">145</col>
      <col name="name">Horst Müller</col>
    </row>
  </table>
  <table tablename="bilder_revisions">
    <row>
      <col name="bilder_reference_id">123</col>
      <col name="author">Tom Mueller</col>
      <col name="comment">Initiale Version</col>
      <col name="created">2011-10-08 14:50</col>
    </row>
    <row>
      <col name="bilder_reference_id">123</col>
      <col name="author">Tom Mueller</col>
      <col name="comment">Rechtschreibfehler korrigiert</col>
      <col name="created">2011-10-08 21:10</col>
    </row>
    <row>
      <col name="bilder_reference_id">123</col>
      <col name="author">Heike Mueller</col>
      <col name="comment">Verberssungen im Detail.</col>
      <col name="created">2011-10-09 09:50</col>
    </row>
    <row>
      <col name="bilder_reference_id">124</col>
      <col name="author">Tom Mueller</col>
      <col name="comment">Initiale Version</col>
      <col name="created">2011-10-08 14:50</col>
    </row>
    <row>
      <col name="bilder_reference_id">124</col>
      <col name="author">Tom Mueller</col>
      <col name="comment">Rechtschreibfehler korrigiert</col>
      <col name="created">2011-10-08 21:10</col>
    </row>
    <row>
      <col name="bilder_reference_id">124</col>
      <col name="author">Heike Mueller</col>
      <col name="comment">Verberssungen im Detail.</col>
      <col name="created">2011-10-09 09:50</col>
    </row>
  </table>
</dump>

Beispiel-Datenmodell für den Import

DROP DATABASE IF EXISTS import_test;
CREATE DATABASE import_test;
\c import_test;
BEGIN;
CREATE TABLE kuenstler (
   id SERIAL PRIMARY KEY NOT NULL,
   name TEXT NOT NULL,
   reference_id INTEGER UNIQUE
);
CREATE TABLE bilder (
    id SERIAL PRIMARY KEY NOT NULL,
    kuenstler_id INTEGER NOT NULL REFERENCES kuenstler(id),
    reference_id INTEGER UNIQUE,
    titel TEXT NOT NULL
);
CREATE TABLE bilder_revision (
    id SERIAL PRIMARY KEY NOT NULL,
    bilder_id INTEGER NOT NULL REFERENCES bilder(id) ON DELETE CASCADE,
	version_no INTEGER NOT NULL,
    comment TEXT,
    author TEXT,
    created TIMESTAMP WITH TIME ZONE
);
CREATE UNIQUE INDEX bilder_rev_uq ON bilder_revision(bilder_id, version_no);
INSERT INTO kuenstler(id, name) VALUES (1, ‘kuenstler 1’);
— INSERT INTO kuenstler(id, name) VALUES (2, ‘kuenstler 2’);
— INSERT INTO kuenstler(id, name) VALUES (3, ‘kuenstler 3’);
SELECT setval(pg_get_serial_sequence(‘kuenstler’, ‘id’), 3);
INSERT INTO bilder(titel, kuenstler_id) VALUES (‘test 1’, 1);
— INSERT INTO bilder(titel, id) VALUES (‘test 2’, 2);
— INSERT INTO bilder(titel, id) VALUES (‘test 3’, 3);
— SELECT * FROM bilder;
— SELECT * FROM kuenstler;
COMMIT;

Ergebnis nach erfolgreichem Import

Nachdem Import unserer Beispiel-Daten sind die Tabellen wie folgt gefüllt:


 id | kuenstler_id | reference_id |       titel
----+--------------+--------------+-------------------
  1 |            1 |              | test 1
  2 |            4 |          123 | Mein erstes Bild
  3 |            5 |          124 | Mein zweites Bild
(3 rows)

 id | bilder_id | version_no |            comment            |    author     |        created
----+-----------+------------+-------------------------------+---------------+------------------------
  1 |         2 |          0 | Initiale Version              | Tom Mueller   | 2011-10-08 14:50:00+02
  2 |         2 |          1 | Rechtschreibfehler korrigiert | Tom Mueller   | 2011-10-08 21:10:00+02
  3 |         2 |          2 | Verberssungen im Detail.      | Heike Mueller | 2011-10-09 09:50:00+02
  4 |         3 |          0 | Initiale Version              | Tom Mueller   | 2011-10-08 14:50:00+02
  5 |         3 |          1 | Rechtschreibfehler korrigiert | Tom Mueller   | 2011-10-08 21:10:00+02
  6 |         3 |          2 | Verberssungen im Detail.      | Heike Mueller | 2011-10-09 09:50:00+02
(6 rows)

 id |     name      | reference_id
----+---------------+--------------
  1 | kuenstler 1   |
  4 | Horst Schramm |          144
  5 | Horst Müller  |          145
(3 rows)