Eine reiche Anfragesprache verspricht ein populäres NoSQL-System auf seiner Startseite. Reicher als SQL? Ist das auffinden von Informationen einfacher, kann ich besser suchen als mit SQL? Das System gehört zur Gruppe der dokumentenbasierten NoSQL-Stores und wirbt mit Schemafreiheit für sich. Es speichert BSON-Dokumente in Sammlungen. Das Dokument lässt sich grob mit einem Datensatz/Tupel aus einer Tabelle/Relation eines RDBMS vergleichen.
Etwas Relationenalgebra
SQL ist eine konkrete Anfragesprache für eine relationale Datenbank. Eine Anfragalgebra kann benutzt werden um eine Anfragesprache mathematisch exakt zu fassen. Das klingt gruselig für den praktisch orientierten Anwender. Es hilft aber, um sich der Frage zu nähern wie reich die eine oder andere Sprache ist und ist auch gleich vorbei. In der Relationenalgebra finden wir:
- Projektion (Ausblenden von Spalten)
- Selektion (Zeilen suchen)
- Verbund (Tabellen verknüpfen)
- Vereinigung (Tabellen vereinigen)
- Differenz (Tabellen abziehen voneinander)
Spalten umbenennen(das ist nichtAS
, brauchen wir in der Praxis nicht)
Einen Teil können wir gleich streichen. Der fragliche NoSQL-Store kann weder Dokumente noch Sammlungen verknüpfen, vereinigen oder die Differenz bilden. Ein Verbund lässt sich bestenfalls aus mehreren Einzelabfragen nachbauen. Irgendwie logisch – er ist eher als hierarchisch denn als relational zu bezeichnen. SQL-Äpfel und NoSQL-Orangen sind zwei verschiedene Dinge.
- Projektion (Ausblenden von Spalten [Feldern])
- Selektion (Zeilen [Dokumente] suchen)
Verbund (Tabellen verknüpfen)Vereinigung (Tabellen vereinigen)Differenz (Tabellen abziehen voneinander)Spalten [Felder] umbenennen
Die Projektion
Im Rahmen der Projektion werden Spalten ausgewählt . Die Projektion findet sich gleich zu Beginn einer SELECT
-Anfrage:
SELECT [DISTINCT] Attribut | arithmetrischer Ausdruck | Aggregatfunktion
Gegeben sei eine Sammlung "leute" mit folgenden Dokumenten:
[ { "hausnummer" : 64, "_id" : { "$oid" : "50520155cc93742e0d0dac65" }, "vorname" : "Ulf" }, { "hausnummer" : 64, "_id" : { "$oid" : "50520263cc93742e0d0dac7e" }, "vorname" : "Gandalf, der Pflegehamster" } ]
In der Relationenalgebra filtert die Projektion Duplikate während SQL Multimengen von Tupeln zulässt. Ein SELET hausnummer FROM leute
liefert zwei identische Ergebniszeilen (Tupel). Das optionale DISTINCT
filtert die Duplikate: SELECT DISTINCT hausnummer FROM leute
liefert dasselbe wie db.leute.distinct("hausnummer")
.
[
{ "hausnummer" : 64 }
]
Statt alle Attribute/Felder/Spalten auszulesen, gilt es nur "vorname" auszugeben. Der entsprechende SQL-Ausdruck wäre SELECT vorname FROM leute
. Im NoSQL-Store lautet die Syntax db.leute.find({}, {vorname:1, _id:0})
.
[ { "vorname" : "Ulf" }, { "vorname" : "Gandalf, der Pflegehamster" } ]
Als nächstes soll die Projektion einen arithmetrischen Ausdruck enthalten, in SQL: SELECT hausnummer + 1 FROM leute
. Eine Entsprechung in der Anfragesprache des NoSQL-Stores konnte ich erst im Rahmen von MapReduce finden. MapReduce und SQL sind zwei verschiedene Kisten. Mehr dazu unten.
SQL unterstützt folgende Aggregatfunktionen: COUNT(*)
, SUM(spalte)
, MAX(spalte)
, MIN(spalte)
, AVG(spalte)
. Der erste ist einfach: db.leute.count()
. Dann wird es spannend, egal welche Funktion gewählt wird. Eine Möglichkeit ist MapReduce. Die zählt nicht. Die zweite Möglichkeit nennt sich nicht MapReduce obschon der Anwender eine "reduce"-Funktion selbst programmieren muß. Hier der Code für SUM(spalte)
: db.leute.group({key: {}, initial: {sum: 0}, reduce: function (doc, out) { out.sum += doc.hausnummer}})
. Der Möglichkeiten nicht genug, gibt es noch eine dritte Syntax. Die kommt aber erst bei einer Gruppierung ins Spiel und ist damit an dieser Stelle raus.
[ { "sum" : 128 } ]
Zwischenergebnis Projektion
Nochmal: MapReduce zählt nicht. Ich vergleiche die eingebaute Anfragesprache an dieser Stelle.
- Projektion (Ausblenden von Spalten): SELECT [DISTINCT] Attribut | arithmetrischer Ausdruck | Aggregatfunktion
- DISTINCT
- Attribut
arithmetrischer Ausdruck- Aggregatfunktion
- COUNT(*)
- SUM(spalte) (Benutzer liefert Funktion)
- MAX(spalte) (Benutzer liefert Funktion)
- MIN(spalte) (Benutzer liefert Funktion)
- AVG(spalte) (Benutzer liefert Funktion)
- Selektion (Zeilen suchen)
Verbund (Tabellen verknüpfen)Vereinigung (Tabellen vereinigen)Differenz (Tabellen abziehen voneinander)Spalten umbenennen
Die einfache Selektion
Mit der WHERE
-Klausel formuliert SQL die Selektion. Diese Betrachtung unterscheidet drei Fälle. Was gruselig aussieht, hilft sogleich beim Vergleich SQL vs. NoSQL-Anfragesprache.
WHERE Konstanten-Selektion: Attribut = | != | < | <= | > | >=Konstante Attribut-Selektion: Attribut = | != | < | <= | > | >= Attribut Verknüpfungen: (Attribut = | != | < | <= | > | >= Konstante|Attribut) und|oder|nicht (...) Ungewißheitsselektion: Attribut LIKE Konstante (Sonderzeichen: %| _) Nullselektion: Attribut IS NULL
Das erste ist einfach: SELECT * FROM leute WHERE hausnummer = 64
entspricht db.leute.find({"hausnummer": : 64})
. Gleichtes gilt für die Paarung SELECT * FROM leute WHERE hausnummer > 1
und db.leute.find({"hausnummer" : {$gt:1}})
.
Ein Vergleich von zwei Attrributen ist im NoSQL-Fall nur dann möglich, wenn der Anwender einen JavaScript-Ausdruck bereitstellt. Platt gesagt: der Anwender muß eine Funktion schreiben. Die Entsprechung eines SELECT * FROM leute WHERE hausnummer != vorname
ist db.leute.find({$where : "this.hausnummer != this.vorname"})
. Das Handbuch des NOSQL-Store merkt an, daß die Selektion mittels $where
langsamer ist als die Verwendung der eingebauten Vergleiche.
Im SQL gibt es Verknüpfungen von Bedingungen mit AND
, OR
und NOT
. Auf der anderen Seite sind die Namen $and
, $or
und $nor
. Schachtelung funktioniert, Gleichstand.
LIKE
bietet der NoSQL-Store nicht. Dafür einen auf regulären Ausdrücken basierten Textvergleich. LIKE
kann mittels eines regulären Ausdrucks emuliert werden. Selbstverständlich kann auch MySQL reguläre Ausdrücke auswerten: RLIKE.
Ohne Schemata müssen drei Fälle beim Test auf NULL
unterschieden werden: Dokumente bei denen das Attribut fehlt oder NULL
enthält, Dokumente bei denen das Attribut vorhanden ist und den Wert NULL
hat, Dokumente bei denen das Attribut nicht vorhanden ist. Alle drei Fälle sind abgedeckt. Damit lässt sich SQL IS NULL
ausdrücken.
Zwischenfazit nach der einfachen Selektion
Wieder einmal muß der Anwender selbst Funktionen schreiben, das meiste geht jedoch.
- Projektion (Ausblenden von Spalten): SELECT [DISTINCT] Attribut | arithmetrischer Ausdruck | Aggregatfunktion
- DISTINCT
- Attribut
arithmetrischer Ausdruck- Aggregatfunktion
- COUNT(*)
- SUM(spalte) (Benutzer liefert Funktion)
- MAX(spalte) (Benutzer liefert Funktion)
- MIN(spalte) (Benutzer liefert Funktion)
- AVG(spalte) (Benutzer liefert Funktion)
- Selektion (Zeilen suchen): WHERE Konstanten-Selektion: Attribut = | != | < | <= | > | >=Konstante.. (s.o.)
- Konstanten-Selektion: Attribut = | != | < | <= | > | >=Konstante
- Attribut-Selektion: Attribut = | != | < | <= | > | >= Attribut (Benutzer liefert Ausdruck/Funktion)
- Verknüpfungen: (Attribut = | != | < | <= | > | >= Konstante|Attribut) und|oder|nicht (…)
- Ungewißheitsselektion: Attribut LIKE Konstante (Sonderzeichen: %| _) (Emulation über Regular Expression, MySQL-Gegenstück: RLIKE)
- Nullselektion: Attribut IS NULL
Verbund (Tabellen verknüpfen)Vereinigung (Tabellen vereinigen)Differenz (Tabellen abziehen voneinander)Spalten umbenennen
Noch mehr zur Selektion: die geschachtelte Anfrage
Bereits in SQL-89 gibt es Unteranfragen, geschachtelte Anfragen, die unter WHERE
vorkommen können.
WHERE ... Quantifizierte Bedingung: ALL, ANY, IN, EXISTS
"Gibt’s doch!" mögen jetzt alle Leser aus rufen, die den Namen des NoSQL-Stores längt kennen. Falsch! Es gibt die Prädikate, es gibt keine geschachtelten Anfragen für, die Prädikate gedacht sind. db.leute.find({"hausnummer" : { $in : [1, 64] }})
entspricht SELECT * FROM leute WHERE hausnummer IN (1, 64)
.
Hingegen ist folgender SQL-Ausdruck nicht direkt in der Sprache des NoSQL-Store auszudrücken: SELECT * FROM leute WHERE hausnummer IN (SELECT hausnummer FROM pflegeeltern)
. Wie man leicht sieht, kommen wir hier in den Bereich des Verbunds.
Vorläufiges Fazit
Das Fazit ist nicht zu übersehen: SQL ist die reichere Anfragesprache.
- Projektion (Ausblenden von Spalten): SELECT [DISTINCT] Attribut | arithmetrischer Ausdruck | Aggregatfunktion
- DISTINCT
- Attribut
arithmetrischer Ausdruck- Aggregatfunktion
- COUNT(*)
- SUM(spalte) (Benutzer liefert Funktion)
- MAX(spalte) (Benutzer liefert Funktion)
- MIN(spalte) (Benutzer liefert Funktion)
- AVG(spalte) (Benutzer liefert Funktion)
- Selektion (Zeilen suchen): WHERE Konstanten-Selektion: Attribut = | != | < | <= | > | >=Konstante.. (s.o.)
- Konstanten-Selektion: Attribut = | != | < | <= | > | >=Konstante
- Attribut-Selektion: Attribut = | != | < | <= | > | >= Attribut (Benutzer liefert Ausdruck/Funktion)
- Verknüpfungen: (Attribut = | != | < | <= | > | >= Konstante|Attribut) und|oder|nicht (…)
- Ungewißheitsselektion: Attribut LIKE Konstante (Sonderzeichen: %| _) (Emulation über Regular Expression, MySQL-Gegenstück: RLIKE)
- Nullselektion: Attribut IS NULL
- Quantifizierte Bedingung: ALL, ANY, IN, EXISTS (Keine Unteranfragen)
Verbund (Tabellen verknüpfen)Vereinigung (Tabellen vereinigen)Differenz (Tabellen abziehen voneinander)Spalten umbenennen
MapReduce zählt nicht
Das vorläufige Fazit ist schon rot. Beim Fehlen des Selbstverbunds wird manch Programmierer rot. Und bei MapReduce werde ich es. Eine Anfragesprache sollte ad-hoc Formulierungen erlauben ohne ein Programm schreiben zu müssen. MapReduce benutzen bedeutet Funktionen und damit ein Programm zu schreiben. Eine Anfragesprache sollte optimierbar sein. Für MapReduce gibt es praktische keine Optimierungsregeln. Eine Anfragesprache sollte sicher sein indem Sinne, daß sie endlose Schleifen oder Ergebnisse verhindert. Jede Operation soll auf Mengen von Daten gleichzeitig arbeiten und nicht nur über einzelne Sätze/Tupel/Dokumente navigieren.
MapReduce ist raus in dieser Diskussion. Wer MapReduce sagt, der muß sich fragen warum er MySQL nicht ausschließlich über die InnoDB API anspricht. Ob nun JavaScript oder C ist doch egal…
Rant?
Nein. Es gibt einiges zu lernen für MySQL von den NoSQL-Stores. Aber ob diese konkrete Anfragesprache dazu gehört…
Happy hacking!
Pingback: Die wunderbare Welt von Isotopp