Comment puis-je exécuter l’équivalent SQL Join dans MongoDB?

Comment puis-je exécuter l’équivalent SQL Join dans MongoDB?

Par exemple, vous avez deux collections (utilisateurs et commentaires) et je souhaite extraire tous les commentaires avec pid = 444, ainsi que les informations utilisateur pour chacun.

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" } 

Est-il possible de tirer tous les commentaires avec un certain champ (par exemple, … find ({pid: 444})) et les informations utilisateur associées à chaque commentaire en une seule fois?

En ce moment, je reçois d’abord les commentaires qui correspondent à mes critères, puis je trouve tous les uid de cet ensemble de résultats, récupérant les objects utilisateur et les fusionnant avec les résultats du commentaire. On dirait que je le fais mal.

A partir de Mongo 3.2, les réponses à cette question ne sont généralement plus correctes. Le nouvel opérateur $ lookup ajouté au pipeline d’agrégation est essentiellement identique à une jointure externe gauche:

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

De la documentation:

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

Bien sûr, Mongo n’est pas une firebase database relationnelle, et les développeurs font attention à recommander des cas d’utilisation spécifiques pour $ lookup, mais au moins de 3.2, la jointure est maintenant possible avec MongoDB.

Cette page du site officiel mongodb répond exactement à cette question:

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

Lorsque nous affichons notre liste d’histoires, nous devons afficher le nom de l’utilisateur qui a publié l’article. Si nous utilisions une firebase database relationnelle, nous pourrions effectuer une jointure sur les utilisateurs et les magasins, et obtenir tous nos objects en une seule requête. Mais MongoDB ne prend pas en charge les jointures et nécessite parfois une certaine dénormalisation. Ici, cela signifie mettre en cache l’atsortingbut “username”.

Les puristes relationnels peuvent déjà se sentir mal à l’aise, comme si nous violions une loi universelle. Mais gardons à l’esprit que les collections MongoDB ne sont pas équivalentes aux tables relationnelles. chacun sert un objective de conception unique. Une table normalisée fournit un bloc de données atomique et isolé. Un document, cependant, représente plus étroitement un object dans son ensemble. Dans le cas d’un site de nouvelles sociales, on peut affirmer qu’un nom d’utilisateur est insortingnsèque à l’article en cours de publication.

Nous pouvons fusionner / joindre toutes les données dans une seule collection avec une fonction simple en quelques lignes à l’aide de la console client mongodb, et nous pourrions désormais effectuer la requête souhaitée. Ci-dessous un exemple complet,

.- Auteurs:

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

.- Catégories:

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

.- Livres

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

.- Prêt de livres

 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' } ]); 

.- La 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); } ); 

.- Obtenez les nouvelles données de collection:

 db.booksReloaded.find().pretty() 

.- Réponse 🙂

 { "_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" : [ ] } 

J’espère que ces lignes peuvent vous aider.

Vous devez le faire comme vous l’avez décrit. MongoDB est une firebase database non relationnelle et ne prend pas en charge les jointures.

Voici un exemple de «rejoindre» * Collections d’ acteurs et de films :

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

Il utilise la méthode .mapReduce()

* join – une alternative pour rejoindre des bases de données orientées document

Comme d’autres l’ont souligné, vous essayez de créer une firebase database relationnelle à partir de bases de données sans relation, ce que vous ne voulez vraiment pas faire, mais de toute façon, si vous devez le faire, voici une solution que vous pouvez utiliser. Nous faisons d’abord une recherche de foreach sur la collection A (ou dans les utilisateurs de votre cas) et ensuite nous obtenons chaque object en tant qu’object, puis nous utilisons la propriété object (uid de votre cas) pour rechercher dans notre deuxième collection peut le trouver alors nous avons une correspondance et nous pouvons imprimer ou faire quelque chose avec elle. J’espère que cela vous aide et bonne chance 🙂

 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 } }); 

Cela dépend de ce que vous essayez de faire.

Vous l’avez actuellement configuré comme une firebase database normalisée, ce qui est bien, et la manière dont vous le faites est appropriée.

Cependant, il y a d’autres façons de le faire.

Vous pourriez avoir une collection d’articles contenant des commentaires intégrés pour chaque article avec des références aux utilisateurs que vous pouvez interroger de manière itérative. Vous pouvez stocker le nom de l’utilisateur avec les commentaires, vous pouvez les stocker tous dans un seul document.

La chose avec NoSQL est qu’il est conçu pour des schémas flexibles et une lecture et une écriture très rapides. Dans une ferme Big Data classique, la firebase database est le plus gros goulot d’étranglement, vous avez moins de moteurs de firebase database que les serveurs d’applications et frontaux … ils sont plus chers mais plus puissants. La normalisation vient du fait que vous tentez d’économiser de l’espace, mais cela entraîne un coût pour rendre vos bases de données complexes et pour vérifier l’intégrité des relations, en exécutant des opérations en cascade. Tout cela évite aux développeurs des maux de tête s’ils ont conçu la firebase database correctement.

Avec NoSQL, si vous acceptez que la redondance et l’espace de stockage ne posent pas de problèmes en raison de leur coût (à la fois en termes de temps processeur requirejs pour effectuer les mises à jour et de stockage des données supplémentaires), la dénormalisation n’est pas un problème (pour les baies intégrées). des centaines de milliers d’éléments peuvent constituer un problème de performances, mais la plupart du temps, cela ne pose aucun problème). De plus, vous aurez plusieurs serveurs frontaux et applications pour chaque cluster de firebase database. Demandez-leur de procéder à la lourde levée des jointures et laissez les serveurs de firebase database s’en tenir à la lecture et à l’écriture.

TL; DR: Ce que vous faites va bien, et il y a d’autres façons de le faire. Consultez les modèles de modèle de données de la documentation mongodb pour obtenir d’excellents exemples. http://docs.mongodb.org/manual/data-modeling/

Il existe une spécification que beaucoup de pilotes prennent en charge, appelée DBRef.

DBRef est une spécification plus formelle pour créer des références entre documents. DBRefs (généralement) inclut un nom de collection ainsi qu’un identifiant d’object. La plupart des développeurs n’utilisent DBRef que si la collection peut changer d’un document à l’autre. Si votre collection référencée sera toujours la même, les références manuelles décrites ci-dessus sont plus efficaces.

Tiré de la documentation MongoDB: Modèles de données> Référence du modèle de données> Références de firebase database

Vous pouvez joindre deux collections dans Mongo en utilisant la recherche qui est offerte dans la version 3.2. Dans votre cas, la requête serait

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

ou vous pouvez également adhérer en ce qui concerne les utilisateurs, il y aura un petit changement comme indiqué ci-dessous.

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

Cela fonctionnera exactement de la même façon que les jointures gauche et droite en SQL.

Avec la bonne combinaison de $ lookup , $ project et $ match , vous pouvez joindre plusieurs tables sur plusieurs parameters. C’est parce qu’ils peuvent être enchaînés plusieurs fois.

Supposons que nous voulions faire suite ( référence )

 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 

Étape 1: Liez toutes les tables

vous pouvez rechercher autant de tables que vous le souhaitez.

$ lookup – un pour chaque table dans la requête

$ dérouler – car les données sont correctement dénormalisées, sinon enveloppées dans des tableaux

Code Python ..

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

Étape 2: Définir tous les conditionnels

$ project : définit ici toutes les instructions conditionnelles, ainsi que toutes les variables que vous souhaitez sélectionner.

Code Python ..

 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 }} ]) 

Étape 3: Rejoignez tous les conditionnels

$ match – joignez toutes les conditions en utilisant OR ou AND etc. Il peut y en avoir plusieurs.

$ project : débloquer tous les conditionnels

Code Python ..

 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 }} ]) 

Pratiquement toute combinaison de tables, de conditions et de jointures peut être faite de cette manière.

Avant 3.2.6 , Mongodb ne supporte pas la requête de jointure comme mysql. ci-dessous la solution qui fonctionne pour vous.

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

Vous pouvez exécuter des requêtes SQL, y compris rejoindre sur MongoDB avec mongo_fdw de Postgres.

MongoDB n’autorise pas les jointures, mais vous pouvez utiliser des plugins pour gérer cela. Vérifiez le plug-in mongo-join. C’est le meilleur et je l’ai déjà utilisé. Vous pouvez l’installer en utilisant npm directement comme ceci npm install mongo-join . Vous pouvez consulter la documentation complète avec des exemples .

(++) outil vraiment utile lorsque nous devons joindre des collections (N)

(-) on peut appliquer des conditions juste au niveau supérieur de la requête

Exemple

 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 (agrégation)

Effectue une jointure externe gauche à une collection non sécurisée dans la même firebase database pour filtrer les documents de la collection «jointe» pour traitement. A chaque document en entrée, l’étape $ lookup ajoute un nouveau champ de tableau dont les éléments sont les documents correspondants de la collection «jointe». L’étape de recherche $ transmet ces documents remodelés à l’étape suivante. L’étape de recherche $ a les syntaxes suivantes:

Match d’égalité

Pour effectuer une correspondance d’égalité entre un champ des documents d’entrée avec un champ des documents de la collection «join», l’étape de recherche $ a la syntaxe suivante:

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

L’opération correspondrait à l’instruction pseudo-SQL suivante:

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

URL Mongo

playORM peut le faire pour vous en utilisant S-SQL (Scalable SQL), qui ajoute simplement un partitionnement tel que vous pouvez effectuer des jointures au sein des partitions.

Vous pouvez le faire en utilisant le pipeline d’agrégation, mais c’est difficile de l’écrire vous-même.

Vous pouvez utiliser mongo-join-query pour créer automatiquement le pipeline d’agrégation à partir de votre requête.

Voici à quoi ressemblerait votre requête:

 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)) ); 

Votre résultat aura l’object utilisateur dans le champ uid et vous pourrez lier autant de niveaux que vous le souhaitez. Vous pouvez renseigner la référence à l’utilisateur, qui fait référence à une équipe, qui fait référence à autre chose, etc.

Disclaimer : J’ai écrit mongo-join-query pour résoudre ce problème précis.

Je pense que si vous avez besoin de tableaux de données normalisés, vous devez essayer d’autres solutions de firebase database.

Mais j’ai trouvé cette solution pour MOngo sur Git By the way, dans le code des insertions – il a le nom du film, mais pas l’identifiant du film .

Problème

Vous avez une collection d’acteurs avec un tableau des films qu’ils ont réalisés.

Vous voulez générer une collection de films avec un tableau d’acteurs dans chacun.

Quelques exemples de données

  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'] }); 

Solution

Nous devons parcourir chaque film du document Actor et émettre chaque film individuellement.

La prise est ici dans la phase de réduction. Nous ne pouvons pas émettre un tableau à partir de la phase de réduction, nous devons donc créer un tableau Actors à l’intérieur du document “value” renvoyé.

Le 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; } 

Remarquez comment actor_list est en réalité un object javascript qui contient un tableau. Notez également que la carte émet la même structure.

Exécutez la commande suivante pour exécuter la commande / Reduce, imprimez-la dans la collection “pivot” et imprimez le résultat:

printjson (db.actors.mapReduce (mapper, réduire, “pivoter”)); db.pivot.find (). forEach (printjson);

Voici l’exemple de sortie, notez que “Pretty Woman” et “Runaway Bride” ont à la fois “Richard Gere” et “Julia Roberts”.

 { "_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" ] } } 

Non, il ne semble pas que vous vous trompiez. Les jointures MongoDB sont «côté client». Assez comme tu l’as dit:

En ce moment, je reçois d’abord les commentaires qui correspondent à mes critères, puis je trouve tous les uid de cet ensemble de résultats, récupérant les objects utilisateur et les fusionnant avec les résultats du commentaire. On dirait que je le fais mal.

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

Ce n’est pas une “vraie” jointure, mais elle est en réalité beaucoup plus utile qu’une jointure SQL car vous n’avez pas à gérer les doublons pour les jointures “à plusieurs”, au lieu de décorer le jeu sélectionné à l’origine.

Il y a beaucoup de bêtises et de FUD sur cette page. 5 ans plus tard, MongoDB est toujours une chose.

Nous pouvons fusionner deux collections en utilisant la sous-requête mongoDB. Voici l’exemple, Commentaires–

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

Utilisateurs–

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

Sous-requête MongoDB pour JOIN–

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

Obtenir le résultat de la collection nouvellement générée–

 db.newCommentUsers.find().pretty() 

Résultat–

 `{ "_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" } ] }` 

J’espère que cela aidera.