Vielen Dank an Julian Motz für die freundliche Unterstützung beim Peer-Review dieses Artikels.
Einer der größten Unterschiede zwischen SQL- und NoSQL-Datenbanken ist JOIN. In relationalen Datenbanken können Sie mit der SQL JOIN-Klausel Zeilen aus zwei oder mehr Tabellen mithilfe eines gemeinsamen Felds kombinieren. Wenn Sie beispielsweise Tabellen mit books
und publishers
haben, können Sie SQL-Befehle schreiben wie:
SELECT book.title, publisher.nameFROM bookLEFT JOIN book.publisher_id ON publisher.id;
Mit anderen Worten, die Tabelle book
verfügt über ein Feld publisher_id
, das auf das Feld id
in der Tabelle publisher
verweist.
Dies ist praktisch, da ein einziger Verlag Tausende von Büchern anbieten kann. Wenn wir jemals die Details eines Herausgebers aktualisieren müssen, können wir einen einzelnen Datensatz ändern. Die Datenredundanz wird minimiert, da wir die Verlegerinformationen nicht für jedes Buch wiederholen müssen. Diese Technik wird als Normalisierung bezeichnet.
SQL-Datenbanken bieten eine Reihe von Normalisierungs- und Einschränkungsfunktionen, um sicherzustellen, dass Beziehungen aufrechterhalten werden.
NoSQL == Kein JOIN?
Nicht immer …
Dokumentenorientierte Datenbanken wie MongoDB dienen zum Speichern denormalisierter Daten. Idealerweise sollte es keine Beziehung zwischen Sammlungen geben. Wenn in zwei oder mehr Dokumenten dieselben Daten erforderlich sind, müssen diese wiederholt werden.
Dies kann frustrierend sein, da es nur wenige Situationen gibt, in denen Sie niemals relationale Daten benötigen. Glücklicherweise führt MongoDB 3.2 einen neuen Operator $lookup
ein, der eine LEFT-OUTER-JOIN-ähnliche Operation für zwei oder mehr Sammlungen ausführen kann. Aber es gibt einen Haken …
MongoDB-Aggregation
$lookup
ist nur in Aggregationsoperationen zulässig. Stellen Sie sich diese als eine Pipeline von Operatoren vor, die ein Ergebnis abfragen, filtern und gruppieren. Die Ausgabe eines Operators wird als Eingabe für den nächsten verwendet.
Aggregation ist schwieriger zu verstehen als einfachere find
Abfragen und wird im Allgemeinen langsamer ausgeführt. Sie sind jedoch leistungsstark und eine unschätzbare Option für komplexe Suchvorgänge.
Aggregation lässt sich am besten anhand eines Beispiels erklären. Angenommen, wir erstellen eine Social-Media-Plattform mit einer user
-Sammlung. Es speichert die Details jedes Benutzers in separaten Dokumenten. Zum Beispiel:
{ "_id": ObjectID("45b83bda421238c76f5c1969"), "name": "User One", "email: "[email protected]", "country": "UK", "dob": ISODate("1999-09-13T00:00:00.000Z")}
Wir können so viele Felder wie nötig hinzufügen, aber alle MongoDB-Dokumente erfordern ein _id
-Feld mit einem eindeutigen Wert. Der _id
ähnelt einem SQL-Primärschlüssel und wird bei Bedarf automatisch eingefügt.
Unser soziales Netzwerk benötigt jetzt eine post
-Sammlung, in der zahlreiche aufschlussreiche Updates von Benutzern gespeichert sind. Die Dokumente speichern den Text, das Datum, eine Bewertung und einen Verweis auf den Benutzer, der ihn geschrieben hat, in einem Feld user_id
:
{ "_id": ObjectID("17c9812acff9ac0bba018cc1"), "user_id": ObjectID("45b83bda421238c76f5c1969"), "date: ISODate("2016-09-05T03:05:00.123Z"), "text": "My life story so far", "rating": "important"}
Wir möchten nun die letzten zwanzig Beiträge mit einer „wichtigen“ Bewertung aller Benutzer in umgekehrter chronologischer Reihenfolge anzeigen. Jedes zurückgegebene Dokument sollte den Text, die Uhrzeit des Beitrags sowie den Namen und das Land des zugehörigen Benutzers enthalten.
Der MongoDB-Aggregatabfrage wird ein Array von Pipeline-Operatoren übergeben, die jede Operation der Reihe nach definieren. Zuerst müssen wir alle Dokumente aus der post
-Sammlung extrahieren, die die richtige Bewertung mit dem $match
-Filter haben:
{ "$match": { "rating": "important" } }
Wir müssen nun die übereinstimmenden Elemente mit dem Operator $sort
in umgekehrter Datumsreihenfolge sortieren:
{ "$sort": { "date": -1 } }
Da wir nur zwanzig Beiträge benötigen, können wir eine $limit
-Stufe anwenden, sodass MongoDB nur die gewünschten Daten verarbeiten muss:
{ "$limit": 20 }
Wir können jetzt Daten aus der Sammlung user
mit dem neuen Operator $lookup
verknüpfen. Es erfordert ein Objekt mit vier Parametern:
-
localField
: das Nachschlagefeld im Eingabedokument -
from
: die Sammlung zum Mitmachen -
foreignField
: das zu suchende Feld in der Sammlungfrom
-
as
: der Name des Ausgabefelds.
Unser Operator ist daher:
{ "$lookup": { "localField": "user_id", "from": "user", "foreignField": "_id", "as": "userinfo"} }
Dadurch wird in unserer Ausgabe ein neues Feld mit dem Namen userinfo
erstellt. Es enthält ein Array, in dem jeder Wert dem user
-Dokument entspricht:
"userinfo":
Wir haben eine Eins-zu-Eins-Beziehung zwischen post.user_id
und user._id
, da ein Beitrag nur einen Autor haben kann. Daher enthält unser Array userinfo
immer nur ein Element. Wir können den Operator $unwind
verwenden, um ihn in ein Unterdokument zu dekonstruieren:
{ "$unwind": "$userinfo" }
Die Ausgabe wird nun in ein praktischeres Format konvertiert, auf das weitere Operatoren angewendet werden können:
"userinfo": { "name": "User One", "email: "[email protected]", …}
Schließlich können wir den Text, die Uhrzeit des Beitrags, den Namen und das Land des Benutzers mithilfe einer $project
-Stufe in der Pipeline zurückgeben:
{ "$project": { "text": 1, "date": 1, "userinfo.name": 1, "userinfo.country": 1} }
Alles zusammenfügen
Unsere endgültige Aggregatabfrage stimmt mit Posts überein, sortiert nach Reihenfolge, beschränkt sich auf die letzten zwanzig Elemente, verknüpft Benutzerdaten, reduziert das Benutzerarray und gibt nur die erforderlichen Felder zurück. Der vollständige Befehl:
db.post.aggregate();
Das Ergebnis ist eine Sammlung von bis zu zwanzig Dokumenten. Beispielsweise:
Großartig! Ich kann endlich zu NoSQL wechseln!
MongoDB $lookup
ist nützlich und leistungsstark, aber selbst dieses grundlegende Beispiel erfordert eine komplexe Aggregatabfrage. Es ist kein Ersatz für die leistungsfähigere JOIN-Klausel, die in SQL angeboten wird. MongoDB bietet auch keine Einschränkungen; Wenn ein user
-Dokument gelöscht wird, bleiben verwaiste post
-Dokumente erhalten.
Idealerweise sollte der Operator $lookup
selten benötigt werden. Wenn Sie es häufig benötigen, verwenden Sie möglicherweise den falschen Datenspeicher …
Wenn Sie relationale Daten haben, verwenden Sie eine relationale (SQL) Datenbank!
Das heißt, $lookup
ist eine willkommene Ergänzung zu MongoDB 3.2. Es kann einige der frustrierenderen Probleme überwinden, wenn kleine Mengen relationaler Daten in einer NoSQL-Datenbank verwendet werden.