Ahnendaten in der OrientDB

Ahnendaten haben ihre ganz speziellen Eigenschaften und Beschränkungen. Daher ist die Wahl der richtigen Persistenz nicht trivial. Eine interessante Wahl um Ahnendaten zu speichern ist die NoSQL Datenbank OrientDB.

Unter NoSQL Datenbanken versteht man alle Varianten nicht-relationaler Datenbankmodelle. Dies sind dokumentbasierte Ansätze wie CouchDB und MongoDB, Key-Value Stores wie BerkleyDB und Redis, Graphdatenbanken wie Neo4j und Neptun oder datenstromorientiert wie Kafka und PipelineDB.

Für Ahnendaten sind dokumentbasierte Ansätze interessant, da viele Daten inkonsistent und unvollständig sind. Alle Informationen, die es zu einem Ahnen gibt, können in einem Dokument aufgenommen werden, auch wenn es zu vielen anderen Ahnen keine entsprechenden Daten gibt. In einer relationalen Datenbank müssen entsprechende Spalten erst eingefügt werden und haben dann für viele Einträge keine Inhalte.

Andererseits sind für Ahnendaten auch Graphdatenbanken interessant, weil der Stammbaum von Natur aus ein azyklischer Graph ist. Hin und wieder gibt es da zwar Ausnahmen, aber auch das können Graphdatenbanken abbilden.

Warum fällt nun die Wahl auf die NoSQL Datenbank OrientDB? Diese Datenbank hat die Möglichkeit dokumentenbasiert und graphenbasiert zu arbeiten.

Im Folgenden ist ein einfacher Use-Case für die Ahnenforschung dargestellt. Ähnlichkeiten zum Beispiel aus der OrientDB Dokumentation sind nicht zu verleugnen, aber hier geht es nicht um Freunde sondern um Eltern.

Zu Beginn wird das Schema für die eigene Datenbank eingerichtet. In diesem Fall gibt es den Vertex Person und als Edge ParentOf. Der Vertex ist ein Knoten im Graph und die Edge ist ein Kante.

try (OrientDB orient = new OrientDB("remote:localhost", OrientDBConfig.defaultConfig())) {
  try (ODatabaseSession db = orient.open("ancestors", user, password)) {
    OClass person = db.getClass("Person");
    if (person == null) {
      person = db.createVertexClass("Person");
    }
    if (person.getProperty("name") == null) {
      person.createProperty("name", OType.STRING);
      person.createIndex("Person_name_index", OClass.INDEX_TYPE.NOTUNIQUE, "name");
    }
    if (db.getClass("ParentOf") == null) {
      db.createEdgeClass("ParentOf");
    }
  }
}

Das Beispiel wirkt etwas moderner als sein Pendant in der offiziellen Dokumentation, weil hier nicht auf das notwendige Schließen der OrientDB und ODatabaseSession Instanzen hingewiesen wird, sondern gleich das Try-Resource Statements genutzt wird.

In Beispiel erhält der Vertex eine Property name und darauf einen Index. Das wirkt auch für ein Beispiel etwas rudimentär aber hier verbirgt sich eine weitere Stärke der OrientDB. Eine Datenbank kann alle Typen und Attribute definieren und damit schemaful arbeiten. Es geht aber auch schemaless und sogar schema-mixed.

Hier wird schema-mixed gearbeitet, weil bei der Anlage der Personen weitere Properties angegeben werden. Ein weiterer Identifier aus der eigenen GEDCOM Datei und das Geschlecht der Person. Da es nur ein Beispiel ist, fehlen hier Angaben zu Geburt, Taufe, Konfirmation bzw. Kommunion, Hochzeit und Tot. Ein schema-mixed Ansatz erlaubt es sehr einfach auch ausgefallene Felder wie Spitzname, Beruf, Ehrungen und Notizen hinzuzufügen.

try (OrientDB orient = new OrientDB("remote:localhost", OrientDBConfig.defaultConfig())) {
  try (ODatabaseSession db = orient.open("ancestors", user, password)) {
    ancestors.stream().map(person -> {
      OVertex result = db.newVertex("Person");
      result.setProperty("gender", person.isGender());
      result.setProperty("name", person.getName());
      result.setProperty("identifier", person.getIdentifier());
      result.save();
      Optional<OVertex> mother = Optional.of(person.getMother()).map(x -> fetchByIdentifier(db, x));
      if (mother.isPresent()) {
        OEdge edge = mother.get().addEdge(child, "ParentOf");
        edge.save();
      }
      Optional<OVertex> father = Optional.of(parent.getFather()).map(x -> fetchByIdentifier(db, x));
      if (father.isPresent() != null) {
        OEdge edge = father.get().addEdge(child, "ParentOf");
        edge.save();
      }
    };
  }
}

Nachdem eine Person eingefügt wurde, werden die Identifier der Eltern genutzt um sie in der Datenbank zu suchen. Werden Vater bzw. Mutter gefunden, dann wird eine Kante zwischen ihnen und ihrem Kind erzeugt.

Der verwendete Stream von Personen liefert immer Eltern vor ihren Kindern, da die Kante nicht erzeugt werden kann, wenn das Kind vor seinen Eltern gespeichert wird. Da nie alle Eltern bekannt sind, muss dies bei der Erstellung der Kanten berücksichtigt werden.

Informationen über Personen können über SQL ähnliche Befehle abgefragt werden. Die folgende Abfrage liefert als Ergebnis nur den Eintrag zum Autoren, da es nur einen einzigen Jens im Stammbaum gibt.

 select * from Person where name like 'Jens%'

Interessanter sind natürlich Abfragen zur Graphenstruktur. Die folgende Abfrage bestimmt mit TRAVERSE die Pfade ausgehend von der jüngsten Person #38:235 im Stammbaum um dann mit SELECT die jüngsten drei Generationen und die Männer auszublenden.

SELECT FROM (TRAVERSE in("ParentOf") FROM #38:235 MAXDEPTH 13 STRATEGY BREADTH_FIRST) where $depth > 3 and gender = false

Das Ergebnis sind alle weiblichen Ahnen meiner Töchter ohne Mutter und Großmütter.

Anna Marie Luise Charlotte /Reimbach/
Anna Marie Luise Ilsebein /Goldstein/
Margarethe Dorothee /Köhrmann/
Gesina Margaretha /Seemann/
Karlot. L. /Fischer/           
Sophie Friederike Henriette /Gieske/
Anne Katherine Marie /Inderlieth/
Anna Catharine Luise /Frentrup/
Gesche Adelheid /Wilkens/
Leita Rebecca /Meyer/           
Katharina Elisabeth /Uloth/
Marie Luise /Schwenker/
A. M. /Nordkämper/
A. M. Elis. /Pilgrim/
A. Mrg, Elis. /Bökenhörster/
Catharina  Jes. /Brackmann/
Rebecke Margarethe /Klauenburg/
Rebecke Marie (Margarethe) /Dannemann/           
Mette Margret /Rathmann/
Lücke Margarethe /Ballehr/
Catharina Margarethe /Hellweg/
Anna Sophie /Hake/
Anna Maria Dorothea /Brinkmann/
Clara Maria Elisabeth /Wergers/
Lüdeke /Wilkens/
Gesche /Helms/
Anna Margarethe /Remke/
Catharine Margarethe /Siemers/
Gesche Margarethe /Schütte/
Sophia Margarethe Elisabeth /Matthiessen/
Adelheid /Helms/
Gesche /Kastens/
Wöbke /Lübbering/
Gebcke Margarthe /Schmidt/
Gesche /Schröder/
Anna Magdalena /Rencken/
Anna Margret /Tribben/           
Hille /Tribbe-Ehefrau/

Mit den Abfragemöglichkeiten der OrientDB kann leicht experimentiert werden, weil eine hilfreiche Weboberfläche mitgeliefert wird.

Dies war ein kurzer Blick auf die Möglichkeiten der OrientDB und ihrem Potential für den Einsatz in der Stammbaumverwaltung.