Lesezeit: 12 Minuten
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…

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) undIS
(hauptsächlich in der FormIS 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 WHERE
–Doku 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.
Option 1: Link
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
undEMBEDDED
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.
Links erzeugen
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.