Nicola Zunino

Lesezeit: 12 Minuten

Techblog

Tutorial OrientDB, Teil 2: Insert, Select, Update, Delete

OrientDB ist eine multi-model Datenbank von Orient Technologies. Dieser Artikel ist der zweite Teil eines Deep Dives. Insert INSERT erzeugt ein neuen Record, der mehr oder weniger zur einer Klasse passt, letztlich hängt das vom Modus (schema-full oder nicht) ab. Es ist möglich, die Daten in drei Formaten durchzugeben: Alle Beispiele zeigen, wie man Records…

Techblog

OrientDB ist eine multi-model Datenbank von Orient Technologies. Dieser Artikel ist der zweite Teil eines Deep Dives.

  • Im ersten Teil dieser Blog-Serie gebe ich eine kurze Einführung in Datenbank-Management-Systeme (DBMS) und zeige Installation und erste Schritte mit OrientDB. Anschließend springe ich ins Arbeiten am konkreten Beispiel und zeige gängige SQL-Abfragen in OrientDB.
  • In diesem zweiten Teil der Serie geht es weiter mit Insert, SELECT, UPDATE DELETE und Relationen (nein, kein JOIN ;-)).
  • In Teil drei beschäftige ich mich mit Graphen, Klassen und SQL.
  • Teil vier dreht sich um Transaktionen, Nutzer und Rollen, verteilte Architekturen und APIs.

Insert

INSERT erzeugt ein neuen Record, der mehr oder weniger zur einer Klasse passt, letztlich hängt das vom Modus (schema-full oder nicht) ab.

Es ist möglich, die Daten in drei Formaten durchzugeben:

  • SQL-92, ähnelt sehr der relationalen Welt:
INSERT INTO Customer (name, surname, age) VALUES ('Johnny', 'Smith', 32) 
  • OrientDB abgekürzte Version:
INSERT INTO Customer SET name='Johnny', surname='Smith', age=32
  • JSON, typisch für NoSQL-Umgebungen. In diesem Fall wird das Schlüsselwort CONTENT benutzt und es wird ein JSON-Objekt mit Key/Value durchgegeben:
INSERT INTO Customer CONTENT {"name": "Johnny", "surname": "Smith", "age": 32}
  • Auch ein gleichzeitiges INSERT ist möglich:
INSERT INTO Customer (name, surname, age) VALUES ('Johnny', 'Smith', 32), ('Carl', 'Green', 21), ('John', 'Black', 56), ('Sally', 'Grey', 73)
 
 

Alle Beispiele zeigen, wie man Records in eine Klasse hinzufügt. Das Objektmodell von OrientDB benutzt Cluster, also kann ein INSERT auch auf einen bestimmten Cluster agieren. Dafür wird ein Prefix benutzt, und zwar cluster::

INSERT INTO cluster:good_customers (name, surname, age) VALUES ('Johnny', 'Smith', 32)
 

SELECT

Mit SELECT kann man Records aus Klassen oder Clustern ausrufen. Zum Beispiel:

SELECT * FROM Customer
SELECT FROM Customer
SELECT name FROM Customer
 

Die ersten beiden Beispiele zeigen ganze Records (das zweite Beispiel zeigt, wie * in OrientDB optional ist). Das dritte Beispiel wird nur die name Eigenschaft von alle Records ausgeben. Eine Filterung von den mit SELECT angezeigten Records ist durch die WHERE-Klausel möglich:

SELECT * FROM Customer WHERE name='Sandy'
SELECT * FROM Customer WHERE name='Sandy' AND age>=18
 

Bei der WHERE-Klause- kann man mit folgenden Logik-Operatoren filtern:

  • Vergleichsoperatoren: > (grösser), <(kleiner), = (gleich) und die Kombinationen >=, <= und <> (nicht gleich);

  • Logische Operatoren: AND, OR, NOT;

  • einige SQL-typische Operatoren wie: IN (und einer Liste mögliche Werte), BETWEEN (ein Range von Werte), LIKE (ein String wird mit einem pattern konfrontiert mit % als Wildcard für irgendwelche Symbole) und IS (hauptsächlich in der Form IS NULL).# zeigt alle Records IN den name eine der drei angegeben Strings gleicht SELECT * FROM Customer WHERE name IN ['John', 'Paul', 'Ringo'] # zeigt alle Records IN den age zwischen 20 und 50 liegt (20 und 50 inklusiv) SELECT * FROM Customer WHERE age BETWEEN 20 AND 50 # zeigt alle Records IN den name mit der Silbe Jo anfängt SELECT * FROM Customer WHERE name LIKE 'Jo%' # zeigt alle Records IN den name NULL ist SELECT * FROM Customer WHERE name IS NULL  
-# zeigt alle Records IN den name eine der drei angegeben Strings gleicht 
-SELECT * FROM Customer WHERE name IN ['John', 'Paul', 'Ringo'] 
-# zeigt alle Records IN den age zwischen 20 und 50 liegt (20 und 50 inklusiv) 
-SELECT * FROM Customer WHERE age BETWEEN 20 AND 50 
-# zeigt alle Records IN den name mit der Silbe Jo anfängt 
-SELECT * FROM Customer WHERE name LIKE 'Jo%' 
-# zeigt alle Records IN den name NULL ist 
-SELECT * FROM Customer WHERE name IS NULL 

Weitere Filtermöglichkeiten sind in der WHEREDoku zu finden.

Funktionen und Methoden

In SQL ist es möglich, Functions und Methoden zu verwenden. Functions verarbeiten einen oder mehrere Parameter und geben ein Resultat raus; Methoden werden direkt für einen bestimmten Wert durchgeführt.

Beispiele für Functions:

# zeigt das grösste age
SELECT MAX(age) FROM Customer 
# rechnet den durchschnitt age 
SELECT avg(age) FROM Customer 
# zählt alle Records der Customer Klasse
SELECT COUNT(*) FROM Customer 
 

Es gibt viele andere Functions, die im Zusammenhang mit Graphen (Navigation, Analyse und Algorithmen), mathematischen und statistischen Rechnungen und der collection-Verwaltung stehen.

Achtung! In OrientDB ist distinct() eine Function, die den DISTINCT-Schlüsselwort von SQL implementiert:

# zeigt alle name ohne Duplizierungen
SELECT DISTINCT(name) FROM Customer 
 

Wie bereits erwähnt werden Methoden direkt an einem Wert angehängt. Man nutzt Methoden meistens, um Daten zu casten oder einen String zu manipulieren:

# zeigt die ersten zwei Buchstaben eines name großgeschrieben 
SELECT name.toUpperCase().LEFT(2) FROM Customer 
# für jeden surname wird die POSITION vom ersten n angezeigt,
# -1 falls kein n im String ist
SELECT surname.indexOf('n') FROM Customer 
 

Weitere Beispiele für Functions und Methoden kann man in der offiziellen Doku finden.

Gruppierung und Ordnung

Auch in OrientDB ist es möglich, durch die GROUP BY-Klausel Daten zu gruppieren. Zum Beispiel kann man hier sehen, wie viele Customer einen bestimmten surname tragen:

SELECT surname, COUNT(surname) FROM Customer GROUP BY surname
 

Durch ORDER BY ist es möglich, die Output-Daten einer Query beliebig zu ordnen. Hier als Beispiel eine ähnliche Query wie oben, geordnet nach der Verbreitung des surname:

SELECT surname, COUNT(surname) AS how_many FROM Customer GROUP BY surname ORDER BY how_many DESC
 
 

UPDATE

Ein UPDATE sieht in einer klassischen relationalen Datenbank so aus:

UPDATE Customer SET name='Robert' WHERE name='Karl'
 
 

Wie in diesem UPDATE-Befehl gibt man als erstes den Klassen-Namen an, in dem der zu ändernde Record gespeichert ist. Die SET-Direktive sagt, dass name Karl in alle Records durch Robert überschrieben werden soll.

Man kann es aber auch nur für ein bestimmtes Cluster durchführen

UPDATE cluster:from_germany SET name='Robert' WHERE name='Karl'
 

oder für ein bestimmtes Record durch seine Nummer

UPDATE #11:10 SET name='Roberta'
 

In diesem Fall, muss man immer den # verwenden und die Cluster Referenz durchgeben.

Eine sehr spannende und hilfreiche Möglichkeit für die Steuerung von OrientDB durch eine Anwendung ist die Möglichkeit, einen Record im JSON Format durchzugeben:

UPDATE #11:9 CONTENT {"name": "Berhard", "surname": "von Humbolt", "age": 91}
 

Dabei wird die SET Direktive durch CONTENT ersetzt. Man kann so nicht nur bestimmte Records updaten, sondern alle vorhergesehen Arten.

Falls man Daten hinzufügen möchte, statt den Inhalt eines Records zu ersetzen, kann man MERGE statt CONTENT im JSON-Format benutzen:

UPDATE #11:9 MERGE {"address": "Main Street", "city": "New York"}
 

Falls die Felder, die man mit MERGE durchgegeben hat, nicht Teil der Struktur der Klasse sein sollen, ist die Änderung nur möglich, wenn die Klasse nicht im strict mode ist.

Es ist auch möglich, ein ganzes Dokument in einem Feld zu speichern:

UPDATE #11:9 SET home={"address": "Main Street", "city": {"name": "New York", "state": "NY", "nation": "USA"}}
 

Eine weitere Direktive, die man mit UPDATE durchgeben kann, ist INCREMENT:

UPDATE #11:9 INCREMENT quantity=3
 

UPDATE von Maps und Collections

In OrientDB stehen uns Collections und Maps zur Verfügung, die in Records gespeichert werden können. Hier wird eine Klasse definiert, in der ein Record eine Gruppe von Kunden dar stellt. Die Links zu diese Kunden sind im Feld list gespeichert:

CREATE CLASS CustomerGroup
INSERT INTO CustomerGroup CONTENT {"list": ["#11:9"]}
 

Gleichzeitig wurde auch ein neuer Record definiert (mit ID #11:12), der eine Collection von Links zu anderen Records verbindet. Aktuell besteht die Collection nur aus der #11:9 Referenz. Einen neuen Link (z.B. mit ID #11:0) kann man folgendermaßen hinzufügen:

UPDATE #11:12 ADD list=#11:0
 

Links kann man selbstverständlich auch wieder rausnehmen:

UPDATE #11:12 REMOVE list=#11:9
 

Für Maps sind ähnliche Vorgehen durch die UPDATE...PUT und UPDATE...REMOVE– Befehle möglich, um ein Key/Value-Paar hinzuzufügen oder einen Key zu entfernen:

UPDATE CustomerGroup PUT ROLE='President', #11:2
UPDATE CustomerGroup REMOVE ROLE='President'
 

DELETE

Der DELETE-Befehl ermöglicht die Löschung von einem oder mehreren Records, die zu einer Klasse oder einem Cluster gehören, durch die WHERE-Klausel:

DELETE FROM Customer WHERE name='John'
 

oder

DELETE FROM cluster:newCustomer WHERE name='John'
 

Es ist auch möglich, die Anzahl der Löschungen mit der Direktive LIMIT zu begrenzen:

DELETE FROM Customer WHERE name='John' LIMIT 20
 

Relationen abbilden (statt JOIN)

Daten, die in einem RDBMS in verschiedenen Tabellen gespeichert sind, können miteinander verbunden werden. Dafür zeigt man über deren Primary Keys die Verknüpfung mit anderen Records. Ich zeige die drei Paradigmen für relationale Datenbanken am Beispiel von Fußballspielern, die Vereinen zugeordnet werden:

  • Relation 1:1: Zwei Zeilen auf verschiedenen Tabellen werden durch einen der beiden Primary Key verknüpft. Das gilt zum Beispiel, wenn man Details für eine der Zeilen hinzufügen möchte, etwa einen Fußballspieler und seinen Vertrag mit einer Mannschaft: Man könnte eine Spieler-Tabelle und eine Vertrag-Tabelle gestalten, indem man die Primary Key des Vertrags in der Spieler-Tabelle speichert und umgekehrt (Primary Key des Spieler in der Vertrag Tabelle speichern).
  • Relation 1:n: Die Grenzen einer 1:1-Relation fallen schnell auf, etwa, da eine Mannschaft aus mehreren Spieler besteht. In dem Fall ist es nicht egal, wo die Primary Key gespeichert wird: Die Spieler-Tabelle muss den Primary Key der Mannschaft Tabelle beinhalten.
  • Relation n:n: Für Verknüpfungen, in die man von beiden Seiten zu mehreren Records referenzieren kann, braucht man eine generelle Lösung. So kann man zum Beispiel die ganze Karriere eines Spielers abbilden, also mehrere Mannschaften von einem Spieler aus referenzieren und gleichzeitig mehrere Spieler für eine Mannschaft. In dem Fall wird eine dritte Tabelle gebraucht, die die Verknüpfung darstellt und beide Primary Keys beinhaltet.

Das Mechanismus, der die verknüpften Daten aus der Datenbank holt, ist JOIN. Dies ist ein teures Mechanismus, weil die benötigten Ressourcen mit der Record-Zahl wachsen. Die Verwendung von Indizes beschleunigt die Read-Befehle SELECT und manchmal UPDATE, aber dabei werden die Write-Befehle INSERT, DELETE und meistens auch UPDATE langsamer. Diese Lösung ist deswegen bei größeren Datenvolumen nicht beliebt.

OrientDB nutzt JOIN nicht. OrientDB setzt auf die Lösungen Link und Record Embedded aus der NoSQL-Welt.

Dank der Graphen-Struktur sind die Records in OrientDB durch den RID (Record ID) verbunden. Diese Referenzen bestehen aus der Cluster-Nummer und einer Record-Nummer. Dabei ist ein Record eindeutig zu finden und das reicht, um die Records zu verknüpfen.

Falls es nötig sein sollte, zwei Records 1:1 oder 1:n zu verknüpfen, benutzt man nur ein Feld vom Typ LINK.

Falls es eine n:n-Beziehung sein sollte, gibt es Typen, die den tatsächlichen Datenstrukturen entsprechen:

  • LINKLIST: eine geordnete Link-Liste;
  • LINKSET: eine nicht geordnete Link-Liste ohne duplizierte Links;
  • LINKMAP: eine Liste von Key/Value-Einträgen, in der die Keys Strings sind und die Values die Links der Records.

So ist es möglich, nicht nur die Links zu speichern, sondern auch direkt im Typ die Access-Policy zu definieren, der die Relation entspricht.

Option 2: Record Embedded

Ein Link setzt zwei unabhängige Records in Verbindung. Manchmal möchte man einen Record nicht unabhängig von den anderen speichern. In dem Fall arbeitet man nicht mit einer Verknüpfung sondern mit einem Feld, das den ganzen abhängigen Record speichert: also ein embedded Objekt.

Links verknüpfen unabhängige Records mit eigener RID, Record Embedded verknüpft einen Record mit RID mit einen Record Embedded ohne eigenes RID. Verknüpfungen werden dadurch überflüssig und man implementiert so den Composition Pattern.

Auch für Record Embedded gibt es besondere Datentypen:

  • EMBEDDED: ein einzelnen Record Embedded;
  • EMBEDDEDLIST: eine ordnete Liste von Record Embedded;
  • EMBEDDEDSET: eine nicht geordnete Liste von Record Embedded ohne duplizierte Record Embedded;
  • EMBEDDEDMAP: eine Liste von Key/Value-Einträgen, in der die Keys Strings und die Values die Record Embedded sind.

TRAVERSE (Teil 1)

Sobald man Methoden hat, um Verknüpfungen zu speichern, muss man auch ein Mechanismus haben, um wieder an die Daten zu kommen. Da OrientDB kein JOIN hat, wird ein spezifischer SQL-Befehl benutzt und zwar TRAVERSE. Hier ein Überblick:

TRAVERSE ist eine Art “intelligentes” SELECT , das alle Daten von referenzierten Knoten lesen kann. Zum Beispiel könnte man zwei Klassen gestalten: Player und Team:

CREATE CLASS Player;
CREATE PROPERTY Player.name STRING;
CREATE PROPERTY Player.surname STRING;
CREATE PROPERTY Player.id_team LINK;
CREATE CLASS Team
CREATE PROPERTY Team.name STRING
 

So wird ein Team hinzugefügt:

INSERT INTO Team SET name='Bayern München'
 

RID #15:0 ist der RID der Mannschaft und wird uns als Output von der Konsole gezeigt. So wird eine Verknüpfung mit einem Spieler erstellt:

INSERT INTO Player SET name='Manuel', surname='Neuer', id_team=#15:0
 

Mit TRAVERSE bekommt man alle Daten vom Spieler und von der Mannschaft:

TRAVERSE * FROM Player
 

Mehr zu TRAVERSE gibt es hier.

OrientDB und Dokumente: Ein Beispiel

Im folgenden Absatz zeige ich folgende Punkte:

  • Klassen definieren, die auch Felder der Typen LINK und EMBEDDED enthalten, falls es nötig sein sollte;
  • Records verknüpfen;
  • Basis-SQL-Befehle benutzen und TRAVERSE benutzen, um mit Verknüpfungen umgehen zu können.

Das Beispiel ist eine Skill-Matrix, in der Kenntnisse, Ausbildung und Projekterfahrung von Mitarbeiter_innen abgelegt werden.

Es wird eine einfache Datenbank erstellt, mit der man Mitarbeitende und deren Fachkenntnisse, ihr Studium und die Projekte speichert:

  • die Employee Klasse speichert die Basisdaten (z.B. Name und Nachname), das Projekt als embedded Objekt und die Kenntnisse inklusive einer Einstufung von 1 bis 10;
  • die Klasse StudyLevel speichert Studium und Abschlüsse, zum Beispiel Abitur, Bachelor oder Master. Jede_r Mitarbeiter_in soll mit dem eigenen Studium verknüpft sein;
  • die Klasse Project speichert den Namen des Projekts und eine Liste der Angestellten, die mitgearbeitet haben. Dadurch wird eine many-to-many-Verknüpfung entstehen, da in Employee ein Feld hinzufügt wird, wo alle Projekte der Person gespeichert sind.

Klassen und Records

Die Klasse StudyLevel wird so definiert:

CREATE CLASS StudyLevel
	CREATE PROPERTY StudyLevel.level STRING
 

und die Einträge wie folgt gespeichert:

INSERT INTO StudyLevel SET level='Master'
INSERT INTO StudyLevel SET level='Bachelor'
 

So werden die Daten abgefragt:

SELECT FROM StudyLevel
 

Die RIDs werden später für die Verknüpfungen benutzt:

#   |@RID |@CLASS    |level
----+-----+----------+-------
0   |#11:0|StudyLevel|Master
1   |#11:1|StudyLevel|Bachelor
----+-----+----------+-------

So sieht die Employee Klasse aus:

CREATE CLASS Employee
CREATE PROPERTY Employee.name STRING
CREATE PROPERTY Employee.surname STRING
CREATE PROPERTY Employee.studyLevel LINK StudyLevel
CREATE PROPERTY Employee.skill EMBEDDED
CREATE PROPERTY Employee.projects LINKSET
INSERT INTO Employee SET name='Paul', surname='Smith', studyLevel=#11:0, skill={"Java" : 10, "PHP" : 7}
INSERT INTO Employee SET name='John', surname='White', studyLevel=#11:1, skill={"Java" : 6, "PHP" : 8}
INSERT INTO Employee SET name='Vincent', surname='Green', studyLevel=#11:0, skill={"Java" : 7, "PHP" : 7}
INSERT INTO Employee SET name='Hector', surname='Black', studyLevel=#11:0, skill={"Java" : 5, "PHP" : 8}
 

Folgende RIDs werden vom System vergeben: #12:0, #12:1, #12:2 e #12:3. Die Klasse ist die Core Klasse der Datenbank. Um die Verknüpfung zu den StudyLevel Records zu gestalten, wurde ein LINK Feld hinzugefügt, das auch den Target enthält. Die Datenbank kann die eingegebene Daten so gleich validieren. Die Verknüpfung mit den Projekten ist durch ein LINKSET implementiert: Duplikate sind so nicht möglich.

Die Project Klasse sieht folgendermaßen aus:

CREATE CLASS Project
CREATE PROPERTY Project.name STRING
CREATE PROPERTY Project.partecipants LINKSET
INSERT INTO Project SET name='Library'
INSERT INTO Project SET name='Taxes'
 

Die neue Records bekommen folgende RIDs: #13:0 und #13:1.

Um Verknüpfungen zwischen Angestellte und Projekte zu erzeugen, nutzt man den UPDATE-Befehl:

UPDATE #13:0 ADD partecipants=[#12:0, #12:1]
UPDATE #13:1 ADD partecipants=[#12:0, #12:2, #12:3]
UPDATE #12:0 ADD projects=[#13:0, #13:1]
UPDATE #12:1 ADD projects=#13:0
UPDATE #12:2 ADD projects=#13:1
UPDATE #12:3 ADD projects=#13:1
 

Falls man eine Verbindung mit mehreren Records erstellen möchte, ist es möglich die [] Syntax zu nutzen, um ein Array von Ziele zu definieren.

TRAVERSE (Teil 2)

Es wäre jetzt möglich, die Klassen zu manipulieren, in dem man die normalen CRUD-Befehle verwendet. In OrientDB geht es besser mit TRAVERSE. Den Unterschied sieht man, wenn man SELECT FROM Employee oder TRAVERSE * FROM Employee eingibt. Im ersten Fall würde man die vier Records der Employee Klasse angezeigt bekommen, im zweiten Fall würden acht Records angezeigt werden: die vier Angestellten mit den richtigen Abschluss, die zwei Skills und dazu passenden Noten. Das zeigt: TRAVERSE geht den Links nach und zeigt die tatsächlichen Werte.

Mit TRAVERSE studyLevel FROM Employee wird TRAVERSE benutzt, um nur die Links vom Employee Record zum StudyLevel zu folgen, die im studyLevel Feld gespeichert sind.

TRAVERSE kann folgendermaßen benutzt werden:

TRAVERSE * FROM #13:0
 

In diesem Fall würden man ein Projekt sehen, alle Angestellte, die mitgearbeitet haben, usw. TRAVERSE ist wesentlich effizienter als eine JOIN und kann als Basis für eine SELECT verwendet werden, die dann die Daten verfeinert. Zum Beispiel könnte man zählen, wie viele Angestellte mit Bachelor oder Master an einem bestimmten Projekt teilgenommen haben:

SELECT studyLevel.level, COUNT(studyLevel) FROM
	(TRAVERSE partecipants FROM #13:0)
GROUP BY studyLevel
 

Output:

1   |1    |Master
2   |1    |Bachelor

Das war der zweite Teil meines OrientDB-Tutorials. Hier geht’s zum ersten Teil mit Installation, ersten Schritten und gängigen SQL-Abfragen in OrientDB. In Teil drei beschäftige ich mich mit Graphen Klassen, und SQL.

Über den Autor

Nicola Zunino

WE, Web Engineering

Nicola arbeitet seit 2017 bei MaibornWolff. Zuerst war er als Frontend Entwickler tätig, mit Schwerpunkte JavaScript und Frameworks wie Angular und React, entdeckte dann jedoch, dass ihm Architekturen und Analysen, also designen, mehr Spaß macht. Als ehemaliger Dozent, unterstützt er interne Schulungen (Clean Code) und Entwicklung der Kollegen. Als Doktor Ingenieur hat er sich in Daten Optimierung und Datenbanken spezialisiert: Datenanalyse, Pattern Matching und Clustering sind noch seine Leidenschaften. 

LinkedIn: https://www.linkedin.com/in/nicola-zunino-22852037/, Twitter: @NicolaZStrong, Instagram: @nicola_zunino, nicola.zunino@maibornwolff.de