Wie führe ich das SQL-Join-Äquivalent in MongoDB durch?

Wie führe ich das SQL-Join-Äquivalent in MongoDB durch?

Angenommen, Sie haben zwei Sammlungen (Benutzer und Kommentare), und ich möchte alle Kommentare mit pid = 444 zusammen mit den Benutzerinformationen für jeden ziehen.

comments { uid:12345, pid:444, comment="blah" } { uid:12345, pid:888, comment="asdf" } { uid:99999, pid:444, comment="qwer" } users { uid:12345, name:"john" } { uid:99999, name:"mia" } 

Gibt es eine Möglichkeit, alle Kommentare mit einem bestimmten Feld (z. B. … find ({pid: 444})) und den Benutzerinformationen, die jedem Kommentar zugeordnet sind, auf einmal zu ziehen?

Im Moment erhalte ich zuerst die Kommentare, die meinen Kriterien entsprechen, dann finde ich alle UIDs in dieser Ergebnismenge heraus, erhalte die Benutzerobjekte und füge sie mit den Kommentaren zusammen. Scheint so, als würde ich es falsch machen.

Ab Mongo 3.2 sind die Antworten auf diese Frage meist nicht mehr korrekt. Der neue $ lookup-Operator, der der Aggregationspipeline hinzugefügt wurde, ist im Wesentlichen identisch mit einem linken äußeren Join:

https://docs.mongodb.org/master/reference/operator/aggregation/lookup/#pipe._S_lookup

Aus den Dokumenten:

 { $lookup: { from: , localField: , foreignField: , as:  } } 

Natürlich ist Mongo keine relationale database, und die Devs sind vorsichtig, spezifische Anwendungsfälle für $ lookup zu empfehlen, aber zumindest ab 3.2 ist nun ein Beitritt mit MongoDB möglich.

Diese Seite auf der offiziellen mongodb-Seite adressiert genau diese Frage:

http://docs.mongodb.org/ecosystem/tutorial/model-data-for-ruby-on-rails/

Wenn wir unsere Liste mit Storys anzeigen, müssen wir den Namen des Nutzers anzeigen, der die Story gepostet hat. Wenn wir eine relationale database verwenden, können wir einen Join für Benutzer und Speicher durchführen und alle unsere Objekte in einer einzigen Abfrage abfragen. Aber MongoDB unterstützt keine Joins und erfordert daher manchmal ein wenig Denormalisierung. Hier bedeutet das, das Attribut ‘Benutzername’ zwischenzuspeichern.

Relationale Puristen mögen sich bereits unwohl fühlen, als würden wir gegen ein universelles Gesetz verstoßen. Bedenken Sie jedoch, dass MongoDB-Sammlungen nicht den relationalen Tabellen entsprechen. Jedes dient einem einzigartigen Designziel. Eine normalisierte Tabelle stellt einen atomaren, isolierten Datenblock bereit. Ein Dokument repräsentiert jedoch ein Objekt als Ganzes genauer. Im Fall einer Social-News-Site kann argumentiert werden, dass ein Nutzername für die gepostete Geschichte wesentlich ist.

Wir können alle Daten in nur einer Sammlung mit einer einfachen function in wenigen Zeilen unter Verwendung der mongodb-Client-Konsole zusammenführen / verbinden, und jetzt könnten wir in der Lage sein, die gewünschte Abfrage durchzuführen. Unter einem vollständigen Beispiel,

.- Autoren:

 db.authors.insert([ { _id: 'a1', name: { first: 'orlando', last: 'becerra' }, age: 27 }, { _id: 'a2', name: { first: 'mayra', last: 'sanchez' }, age: 21 } ]); 

.- Kategorien:

 db.categories.insert([ { _id: 'c1', name: 'sci-fi' }, { _id: 'c2', name: 'romance' } ]); 

.- Bücher

 db.books.insert([ { _id: 'b1', name: 'Groovy Book', category: 'c1', authors: ['a1'] }, { _id: 'b2', name: 'Java Book', category: 'c2', authors: ['a1','a2'] }, ]); 

.- Buchverleih

 db.lendings.insert([ { _id: 'l1', book: 'b1', date: new Date('01/01/11'), lendingBy: 'jose' }, { _id: 'l2', book: 'b1', date: new Date('02/02/12'), lendingBy: 'maria' } ]); 

.- Die Magie:

 db.books.find().forEach( function (newBook) { newBook.category = db.categories.findOne( { "_id": newBook.category } ); newBook.lendings = db.lendings.find( { "book": newBook._id } ).toArray(); newBook.authors = db.authors.find( { "_id": { $in: newBook.authors } } ).toArray(); db.booksReloaded.insert(newBook); } ); 

.- Holen Sie sich die neuen Daten der Sammlung:

 db.booksReloaded.find().pretty() 

.- Antwort 🙂

 { "_id" : "b1", "name" : "Groovy Book", "category" : { "_id" : "c1", "name" : "sci-fi" }, "authors" : [ { "_id" : "a1", "name" : { "first" : "orlando", "last" : "becerra" }, "age" : 27 } ], "lendings" : [ { "_id" : "l1", "book" : "b1", "date" : ISODate("2011-01-01T00:00:00Z"), "lendingBy" : "jose" }, { "_id" : "l2", "book" : "b1", "date" : ISODate("2012-02-02T00:00:00Z"), "lendingBy" : "maria" } ] } { "_id" : "b2", "name" : "Java Book", "category" : { "_id" : "c2", "name" : "romance" }, "authors" : [ { "_id" : "a1", "name" : { "first" : "orlando", "last" : "becerra" }, "age" : 27 }, { "_id" : "a2", "name" : { "first" : "mayra", "last" : "sanchez" }, "age" : 21 } ], "lendings" : [ ] } 

Ich hoffe diese Zeilen können dir helfen.

Sie müssen es so machen, wie Sie es beschrieben haben. MongoDB ist eine nicht relationale database und unterstützt keine Joins.

Hier ist ein Beispiel für eine “join” * Actors and Movies- Sammlung:

https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt

Es verwendet die .mapReduce() -Methode

* Join – eine Alternative, um in dokumentorientierte databaseen einzutreten

Wie andere darauf hingewiesen haben, versuchen Sie, eine relationale database aus keiner relationalen database zu erstellen, die Sie wirklich nicht machen wollen, aber trotzdem, wenn Sie einen Fall haben, den Sie hier tun müssen, ist dies eine Lösung, die Sie verwenden können. Wir machen zuerst einen foreach-Fund auf Sammlung A (oder in Ihrem Fall Benutzer) und dann bekommen wir jedes Objekt als Objekt, dann benutzen wir Objekteigenschaft (in unserem Fall uid), um in unserer zweiten Sammlung (in Ihrem Fall Kommentare) nachzuschlagen kann es finden, dann haben wir eine Übereinstimmung und wir können etwas drucken oder etwas damit machen. Hoffe das hilft dir und viel Glück 🙂

 db.users.find().forEach( function (object) { var commonInBoth=db.comments.findOne({ "uid": object.uid} ); if (commonInBoth != null) { printjson(commonInBoth) ; printjson(object) ; }else { // did not match so we don't care in this case } }); 

Es hängt davon ab, was Sie versuchen zu tun.

Sie haben es zur Zeit als eine normalisierte database eingerichtet, was in Ordnung ist und die Art, wie Sie es tun, ist angemessen.

Es gibt jedoch andere Möglichkeiten, dies zu tun.

Sie könnten eine Postsammlung mit eingebetteten Kommentaren für jeden Post mit Verweisen auf die Benutzer haben, die Sie iterativ abfragen können. Sie könnten den Namen des Benutzers mit den Kommentaren speichern, Sie könnten sie alle in einem Dokument speichern.

Die Sache mit NoSQL ist, dass es für flexible Schemata und sehr schnelles Lesen und Schreiben entwickelt wurde. In einer typischen Big Data-Farm ist die database der größte Engpass, Sie haben weniger database-Engines als Sie Anwendungen und Front-End-Server … sie sind teurer, aber leistungsfähiger, auch Festplattenspeicher ist vergleichsweise günstig. Die Normalisierung ergibt sich aus dem Konzept, Platz zu sparen, aber es kostet Ihre databaseen, komplizierte Joins durchzuführen und die Integrität von Beziehungen zu überprüfen, indem Sie kaskadierende Operationen ausführen. All das erspart den Entwicklern einige Probleme, wenn sie die database richtig entworfen haben.

Wenn Sie bei NoSQL akzeptieren, dass Redundanz und Speicherplatz keine Probleme aufgrund ihrer Kosten sind (sowohl die processorzeit für Aktualisierungen als auch die Festplattenkosten für die Speicherung zusätzlicher Daten), ist Denormalisierung kein Problem (für eingebettete Arrays, die zu Hunderttausende von Items kann es ein performancesproblem sein, aber die meiste Zeit ist das kein Problem). Darüber hinaus verfügen Sie über mehrere Anwendungs- und Front-End-Server für jeden databasecluster. Lassen Sie die Joins mühsam anheben und lassen Sie die databaseserver beim Lesen und Schreiben hängen.

TL: DR: Was Sie tun, ist in Ordnung, und es gibt andere Möglichkeiten, es zu tun. Schau dir die Datenmodell-Muster der mongodb-Dokumentation an, um einige großartige Beispiele zu sehen. http://docs.mongodb.org/manual/data-modeling/

Es gibt eine Spezifikation, die viele Treiber unterstützen, die DBRef genannt wird.

DBRef ist eine formellere Spezifikation zum Erstellen von Referenzen zwischen Dokumenten. DBRefs enthalten (allgemein) einen Sammlungsnamen sowie eine Objekt-ID. Die meisten Entwickler verwenden nur DBRefs, wenn die Sammlung von einem Dokument zum nächsten wechseln kann. Wenn Ihre referenzierte Sammlung immer dieselbe ist, sind die oben beschriebenen manuellen Referenzen effizienter.

Aus der MongoDB-Dokumentation: Datenmodelle> Datenmodellreferenz> databasereferenzen

Sie können zwei Sammlungen in Mongo verbinden, indem Sie eine Suche verwenden, die in Version 3.2 angeboten wird. In Ihrem Fall wäre die Abfrage

 db.comments.aggregate({ $lookup:{ from:"users", localField:"uid", foreignField:"uid", as:"users_comments" } }) 

oder Sie können auch in Bezug auf Benutzer beitreten, dann wird es eine kleine Änderung geben, wie unten angegeben.

 db.users.aggregate({ $lookup:{ from:"comments", localField:"uid", foreignField:"uid", as:"users_comments" } }) 

Es funktioniert genau wie links und rechts in SQL verbinden.

Mit der richtigen Kombination von $ lookup , $ project und $ match können Sie mehreren Tabellen für mehrere Parameter beitreten. Dies liegt daran, dass sie mehrmals verkettet werden können.

Angenommen, wir wollen folgendes tun ( Referenz )

 SELECT S.* FROM LeftTable S LEFT JOIN RightTable R ON S.ID =R.ID AND S.MID =R.MID WHERE R.TIM >0 AND S.MOB IS NOT NULL 

Schritt 1: Verknüpfen Sie alle Tabellen

Sie können so viele Tabellen nachschlagen, wie Sie möchten.

$ lookup – eins für jede Tabelle in der Abfrage

$ unwind – weil Daten korrekt denormalisiert werden, sonst in Arrays verpackt

Python-Code ..

 db.LeftTable.aggregate([ # connect all tables {"$lookup": { "from": "RightTable", "localField": "ID", "foreignField": "ID", "as": "R" }}, {"$unwind": "R"} ]) 

Schritt 2: Definieren Sie alle Bedingungen

$ project : Definieren Sie hier alle bedingten statementen sowie alle Variablen, die Sie auswählen möchten.

Python-Code ..

 db.LeftTable.aggregate([ # connect all tables {"$lookup": { "from": "RightTable", "localField": "ID", "foreignField": "ID", "as": "R" }}, {"$unwind": "R"}, # define conditionals + variables {"$project": { "midEq": {"$eq": ["$MID", "$R.MID"]}, "ID": 1, "MOB": 1, "MID": 1 }} ]) 

Schritt 3: Verbinden Sie alle Bedingungen

$ match – verbinde alle Bedingungen mit OR oder AND usw. Es kann mehrere davon geben.

$ Projekt : Alle Bedingungen aufheben

Python-Code ..

 db.LeftTable.aggregate([ # connect all tables {"$lookup": { "from": "RightTable", "localField": "ID", "foreignField": "ID", "as": "R" }}, {"$unwind": "$R"}, # define conditionals + variables {"$project": { "midEq": {"$eq": ["$MID", "$R.MID"]}, "ID": 1, "MOB": 1, "MID": 1 }}, # join all conditionals {"$match": { "$and": [ {"R.TIM": {"$gt": 0}}, {"MOB": {"$exists": True}}, {"midEq": {"$eq": True}} ]}}, # undefine conditionals {"$project": { "midEq": 0 }} ]) 

Auf diese Weise kann so ziemlich jede Kombination von Tabellen, Bedingungen und Verknüpfungen durchgeführt werden.

Vor 3.2.6 unterstützt Mongodb die Join-Abfrage nicht wie mysql. unter Lösung, die für Sie arbeitet.

  db.getCollection('comments').aggregate([ {$match : {pid : 444}}, {$lookup: {from: "users",localField: "uid",foreignField: "uid",as: "userData"}}, ]) 

Sie können SQL-Abfragen ausführen, einschließlich der Verbindung zu MongoDB mit mongo_fdw von Postgres.

MongoDB lässt Joins nicht zu, aber Sie können Plugins dafür verwenden. Überprüfen Sie das mongo-join-Plugin. Es ist das Beste und ich habe es bereits benutzt. Sie können es mit npm direkt npm install mongo-join wie diese npm install mongo-join . Sie können die vollständige Dokumentation anhand von Beispielen überprüfen.

(++) wirklich hilfreiches Werkzeug, wenn wir (N) Sammlungen beitreten müssen

(-) Wir können Bedingungen nur auf der obersten Ebene der Abfrage anwenden

Beispiel

 var Join = require('mongo-join').Join, mongodb = require('mongodb'), Db = mongodb.Db, Server = mongodb.Server; db.open(function (err, Database) { Database.collection('Appoint', function (err, Appoints) { /* we can put conditions just on the top level */ Appoints.find({_id_Doctor: id_doctor ,full_date :{ $gte: start_date }, full_date :{ $lte: end_date }}, function (err, cursor) { var join = new Join(Database).on({ field: '_id_Doctor', // < - field in Appoints document to: '_id', // <- field in User doc. treated as ObjectID automatically. from: 'User' // <- collection name for User doc }).on({ field: '_id_Patient', // <- field in Appoints doc to: '_id', // <- field in User doc. treated as ObjectID automatically. from: 'User' // <- collection name for User doc }) join.toArray(cursor, function (err, joinedDocs) { /* do what ever you want here */ /* you can fetch the table and apply your own conditions */ ..... ..... ..... resp.status(200); resp.json({ "status": 200, "message": "success", "Appoints_Range": joinedDocs, }); return resp; }); }); 

$ lookup (Aggregation)

Führt einen linken äußeren Join zu einer nicht gehärteten Sammlung in derselben database aus, um Dokumente aus der Sammlung “joined” zur Verarbeitung zu filtern. Zu jedem Eingabedokument fügt die $ lookup-Phase ein neues Array-Feld hinzu, dessen Elemente die übereinstimmenden Dokumente aus der Sammlung “joined” sind. Die $ lookup-Phase übergibt diese neu gestalteten Dokumente an die nächste Stufe. Die $ lookup-Phase hat die folgenden Syntaxen:

Gleichheitsmatch

Um eine Gleichheitsübereinstimmung zwischen einem Feld aus den Eingabedokumenten und einem Feld aus den Dokumenten der “Joined” -Auflistung durchzuführen, weist die $ lookup-Phase die folgende Syntax auf:

 { $lookup: { from: , localField: , foreignField: , as:  } } 

Die Operation würde der folgenden Pseudo-SQL-statement entsprechen:

 SELECT *,  FROM collection WHERE  IN (SELECT  FROM  WHERE  ); 

Mongo-URL

playORM kann das für Sie mit S-SQL (Scalable SQL) tun, das nur Partitionierung hinzufügt, so dass Sie Joins innerhalb von Partitionen machen können.

Sie können es mit der Aggregationspipeline machen, aber es ist ein Schmerz, es selbst zu schreiben.

Sie können mongo-join-query , um die Aggregationspipeline automatisch aus Ihrer Abfrage zu erstellen.

So würde Ihre Anfrage aussehen:

 const mongoose = require("mongoose"); const joinQuery = require("mongo-join-query"); joinQuery( mongoose.models.Comment, { find: { pid:444 }, populate: ["uid"] }, (err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results)) ); 

Ihr Ergebnis hätte das Benutzerobjekt im Feld ” uid und Sie können beliebig viele Ebenen verknüpfen. Sie können den Verweis auf den Benutzer auffüllen, der auf ein Team verweist, das auf etwas anderes verweist, usw.

Haftungsausschluss : Ich habe mongo-join-query , um dieses Problem zu lösen.

Ich denke, wenn Sie normalisierte Datentabellen benötigen – Sie müssen einige andere databaselösungen versuchen.

Aber ich habe diese Lösung für MOngo on Git übrigens in Inserts Code – es hat den Namen des Films, aber noi Film ID .

Problem

Du hast eine Sammlung von Schauspielern mit einer Reihe von Filmen, die sie gemacht haben.

Sie möchten eine Sammlung von Filmen mit einer Reihe von Akteuren erstellen.

Einige Beispieldaten

  db.actors.insert( { actor: "Richard Gere", movies: ['Pretty Woman', 'Runaway Bride', 'Chicago'] }); db.actors.insert( { actor: "Julia Roberts", movies: ['Pretty Woman', 'Runaway Bride', 'Erin Brockovich'] }); 

Lösung

Wir müssen jeden Film im Actor-Dokument durchlaufen und jeden Film einzeln ausgeben.

Der Haken ist hier in der Reduzierphase. Wir können kein Array aus der reduce-Phase ausgeben, daher müssen wir innerhalb des “value” -Dokuments, das zurückgegeben wird, ein Actors-Array erstellen.

Der Code

 map = function() { for(var i in this.movies){ key = { movie: this.movies[i] }; value = { actors: [ this.actor ] }; emit(key, value); } } reduce = function(key, values) { actor_list = { actors: [] }; for(var i in values) { actor_list.actors = values[i].actors.concat(actor_list.actors); } return actor_list; } 

Beachten Sie, dass actor_list tatsächlich ein JavaScript-Objekt ist, das ein Array enthält. Beachten Sie auch, dass die Karte die gleiche Struktur aufweist.

Führen Sie folgenden Befehl aus, um den Befehl map / reduce auszuführen, geben Sie ihn in der Sammlung “pivot” aus und drucken Sie das Ergebnis:

printjson (db.actors.mapReduce (map, reduce, “pivot”)); db.pivot.find (). forEach (printjson);

Hier ist die Beispielausgabe, beachten Sie, dass “Pretty Woman” und “Runaway Bride” sowohl “Richard Gere” als auch “Julia Roberts” haben.

 { "_id" : { "movie" : "Chicago" }, "value" : { "actors" : [ "Richard Gere" ] } } { "_id" : { "movie" : "Erin Brockovich" }, "value" : { "actors" : [ "Julia Roberts" ] } } { "_id" : { "movie" : "Pretty Woman" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } } { "_id" : { "movie" : "Runaway Bride" }, "value" : { "actors" : [ "Richard Gere", "Julia Roberts" ] } } 

Nein, es scheint nicht so, als ob du es falsch machst. MongoDB-Joins sind “Client-Seite”. So ziemlich wie du gesagt hast:

Im Moment erhalte ich zuerst die Kommentare, die meinen Kriterien entsprechen, dann finde ich alle UIDs in dieser Ergebnismenge heraus, erhalte die Benutzerobjekte und füge sie mit den Kommentaren zusammen. Scheint so, als würde ich es falsch machen.

 1) Select from the collection you're interested in. 2) From that collection pull out ID's you need 3) Select from other collections 4) Decorate your original results. 

Es ist kein “echter” Join, aber es ist tatsächlich viel nützlicher als ein SQL-Join, da Sie sich nicht mit doppelten Zeilen für “viele” Seiten-Joins befassen müssen, sondern stattdessen den ursprünglich ausgewählten Satz dekorieren.

Es gibt viel Unsinn und FUD auf dieser Seite. Es stellt sich heraus, 5 Jahre später MongoDB ist immer noch eine Sache.

Wir können zwei Sammlungen zusammenführen, indem wir die mongoDB-Unterabfrage verwenden. Hier ist ein Beispiel, Commentss–

 `db.commentss.insert([ { uid:12345, pid:444, comment:"blah" }, { uid:12345, pid:888, comment:"asdf" }, { uid:99999, pid:444, comment:"qwer" }])` 

Benutzer–

 db.userss.insert([ { uid:12345, name:"john" }, { uid:99999, name:"mia" }]) 

MongoDB-Unterabfrage für JOIN–

 `db.commentss.find().forEach( function (newComments) { newComments.userss = db.userss.find( { "uid": newComments.uid } ).toArray(); db.newCommentUsers.insert(newComments); } );` 

Erhalte das Ergebnis einer neu generierten Collection–

 db.newCommentUsers.find().pretty() 

Ergebnis–

 `{ "_id" : ObjectId("5511236e29709afa03f226ef"), "uid" : 12345, "pid" : 444, "comment" : "blah", "userss" : [ { "_id" : ObjectId("5511238129709afa03f226f2"), "uid" : 12345, "name" : "john" } ] } { "_id" : ObjectId("5511236e29709afa03f226f0"), "uid" : 12345, "pid" : 888, "comment" : "asdf", "userss" : [ { "_id" : ObjectId("5511238129709afa03f226f2"), "uid" : 12345, "name" : "john" } ] } { "_id" : ObjectId("5511236e29709afa03f226f1"), "uid" : 99999, "pid" : 444, "comment" : "qwer", "userss" : [ { "_id" : ObjectId("5511238129709afa03f226f3"), "uid" : 99999, "name" : "mia" } ] }` 

Hoffe, das wird helfen.