Comment réaliser L'équivalent SQL Join en MongoDB?
comment réaliser L'équivalent SQL Join en MongoDB?
par exemple dire que vous avez deux collections (utilisateurs et commentaires) et je veux tirer tous les commentaires avec pid=444 avec 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 un moyen de tirer tous les commentaires avec un certain champ (par ex. ...trouver ({pid: 444})) et les informations utilisateur associées à chaque commentaire en une seule fois?
en ce moment, je commence à avoir les commentaires qui correspondent à mes critères, en calculant ensuite tous les uid dans ce jeu de résultats, en obtenant les objets utilisateur, et en les fusionnant avec les résultats du commentaire. On dirait que je suis de faire le mal.
19 réponses
à 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
à Partir de la documentation:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
bien sûr Mongo est pas une base de données relationnelle, et les devs sont prudents de recommander des cas d'utilisation spécifiques pour $lookup, mais au moins sur 3.2 doing join est maintenant possible avec MongoDB.
cette page sur le site officiel mongodb adresses exactement cette question:
http://docs.mongodb.org/ecosystem/tutorial/model-data-for-ruby-on-rails /
quand nous afficherons notre liste d'histoires, nous aurons besoin de montrer le nom de l'utilisateur qui a posté l'histoire. Si nous utilisions une base de données relationnelle, nous pourrions effectuer une jointure sur les utilisateurs et les magasins, et obtenir tous nos objets dans un seule requête. Mais MongoDB ne supporte pas les jointures et donc, à certains moments, nécessite un peu de dénormalisation. Ici, cela signifie mettre en cache l'attribut "nom d'utilisateur".
les puristes relationnels peuvent se sentir déjà mal à l'aise, comme si nous violions une loi universelle. Mais gardons à l'esprit que les collections MongoDB ne sont pas équivalentes à des tables relationnelles; chacune sert un objectif de conception unique. Une table normalisée fournit un bloc atomique et isolé de données. Un document, cependant, plus représente étroitement un objet dans son ensemble. Dans le cas d'un site de nouvelles sociales, on peut soutenir qu'un nom d'utilisateur est intrinsèque à l'article affiché.
nous pouvons fusionner/joindre toutes les données à l'intérieur d'une seule collection avec une fonction facile en quelques lignes en utilisant la console client mongodb, et maintenant nous pourrions être en mesure d'effectuer la requête désiré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 151980920"
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);
}
);
.- Obtenir les nouvelles données de collecte:
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 base de données non relationnelle et ne supporte pas les jointures.
Voici un exemple de "join" * Acteurs et Films collections:
https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt
il fait usage de .mapReduce()
méthode
* joignez-vous à - une alternative à la rejoindre dans les bases de données orientées document
comme d'autres l'ont fait remarquer, vous essayez de créer une base de données relationnelle à partir d'aucune base de données relationnelle que vous ne voulez vraiment pas faire, mais de toute façon, si vous avez un cas que vous devez faire ceci voici une solution que vous pouvez utiliser. Nous faisons d'abord un foreach trouver sur la collection A (ou dans votre cas des utilisateurs) et puis nous obtenons chaque élément comme un objet puis nous utilisons la propriété d'objet (dans votre cas uid) pour chercher dans notre deuxième collection (dans votre cas commentaires) si nous pouvons le trouver alors nous avons une correspondance et nous pouvez imprimer ou faire quelque chose avec elle. Espérons que cela vous aide, vous 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.
actuellement, il est défini comme une base de données normalisée, ce qui est bien, et la façon dont vous le faites est approprié.
Cependant, il y a d'autres façons de le faire.
vous pourriez avoir une collection de messages qui a intégré des commentaires pour chaque message avec des références aux utilisateurs que vous pouvez itérativement requête pour obtenir. Vous pouvez stocker le nom de l'utilisateur avec les commentaires, vous pouvez les stocker dans un seul document.
le truc avec NoSQL est qu'il est conçu pour les schémas flexibles et la lecture et l'écriture très rapide. Dans une ferme de Big Data typique la base de données est le plus grand goulot d'étranglement, vous avez moins de moteurs de base de données que vous ne le faites application et serveurs front end...ils sont plus chers mais plus puissants, aussi l'espace disque dur est très bon marché comparativement. La normalisation vient du concept d'essayer de sauver de l'espace, mais il vient avec un coût à faire votre les bases de données effectuent des jointures compliquées et vérifient l'intégrité des relations, en effectuant des opérations en cascade. Tout ce qui sauve les développeurs quelques maux de tête s'ils ont conçu la base de données correctement.
avec NoSQL, si vous acceptez que la redondance et l'espace de stockage ne sont pas des problèmes en raison de leur coût (à la fois dans le temps de processeur nécessaire pour faire des mises à jour et les coûts de disque dur pour stocker des données supplémentaires), la dénormalisation n'est pas un problème (pour les tableaux intégrés qui deviennent des centaines de des milliers d'articles, il peut être un problème de performances, mais la plupart du temps ce n'est pas un problème). En outre, vous aurez plusieurs applications et serveurs de première ligne pour chaque grappe de base de données. Demandez-leur de faire le levage lourd des jointures et de laisser les serveurs de base de données coller à la lecture et l'écriture.
TL; DR: ce que vous faites est très bien, et il y a d'autres façons de le faire. Consultez les modèles de modèles de données de la documentation mongodb pour quelques exemples intéressants. http://docs.mongodb.org/manual/data-modeling /
il y a une spécification que beaucoup de pilotes supportent qui s'appelle DBRef.
DBRef est un plus de spécification formelle pour la création de références entre les documents. Les DBRefs (généralement) incluent un nom de collection ainsi qu'un id d'objet. La plupart des développeurs n'utilisent DBRefs 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: Data Models > Data Model Reference > Références De La Base De Données
vous pouvez joindre deux collections dans Mongo en utilisant la recherche qui est offert en 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 rejoindre en ce qui concerne les utilisateurs alors il y aura un peu de changement comme indiqué ci-dessous.
db.users.aggregate({
$lookup:{
from:"comments",
localField:"uid",
foreignField:"uid",
as:"users_comments"
}
})
il fonctionnera tout comme gauche et droite rejoignent en SQL.
Avec la bonne combinaison de $recherche , $projet et $match , vous pouvez joindre plusieurs tables sur plusieurs paramètres. C'est parce qu'ils peuvent être enchaînés à plusieurs reprises.
supposons que nous voulons suivre ( 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: lier toutes les tables
vous pouvez $recherche comme beaucoup tables comme vous voulez.
$ lookup - un pour chaque table dans query
$unwind - parce que les données sont dénormalisées correctement, autrement enveloppé 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 toutes les conditions
$project : définissez tous les énoncés conditionnels ici, plus tous 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: joindre toutes les conditions
$match - joindre toutes les conditions en utilisant OR et et etc. Il y a peut être des multiples de ces.
$projet : la destruction de toutes les conditions
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
}}
])
À peu près n'importe quelle combinaison de tables, conditionnels et jointures peut être fait de cette manière.
avant 3.2.6 , Mongodb ne supporte pas join query 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"}},
])
$recherche (agrégation)
Effectue une jointure externe gauche à une collection non protégée dans la même base de données pour filtrer dans les documents de la collection "jointe" pour le traitement. Pour chaque document d'entrée, l'étape $ lookup ajoute un nouveau champ array dont les éléments sont les documents correspondants de la collection "joined". L'étape $lookup fait passer ces documents remaniés à l'étape suivante. L'étape $ lookup a les syntaxes suivantes::
égalité Match
Pour effectuer une correspondance d'égalité entre un champ des documents entrants et un champ des documents de la collection "joint", l'étape $lookup a la syntaxe suivante::
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
l'opération correspondrait à la déclaration pseudo-SQL suivante:
SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT <documents as determined from the pipeline>
FROM <collection to join>
WHERE <pipeline> );
MongoDB n'autorise pas les jointures, mais vous pouvez utiliser des plugins pour gérer cela. Vérifiez le plugin 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 quand nous avons besoin de joindre (N) collections
( -- ) nous pouvons 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;
});
});
playORM peut le faire pour vous en utilisant S-SQL(Scalable SQL) qui ajoute juste le partitionnement tel que vous pouvez faire des jointures dans les partitions.
vous pouvez le faire en utilisant le pipeline d'agrégation, mais c'est pénible de l'écrire vous-même.
vous pouvez utiliser mongo-join-query
pour créer le pipeline d'agrégation automatiquement à 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 aurait l'objet utilisateur dans le champ uid
et vous pouvez lier autant de niveaux profonds que vous voulez. Vous pouvez peupler le 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 aborder ce problème exact.
je pense, si vous avez besoin de tables de données normalisées - vous devez essayer d'autres solutions de base de données.
mais j'ai trouvé cette solution pour MOngo sur Git Au fait, dans les inserts code - il a le nom du film, mais noi ID du film .
problème
vous avez une collection d'acteurs avec un tableau des films qu'ils ont fait.
Vous voulez générer un collection de Films avec un éventail d'Acteurs dans chaque.
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 avons besoin de boucler chaque film dans le document de L'acteur et d'émettre chaque film individuellement.
la prise ici est dans la phase de réduction. Nous ne pouvons pas émettre un tableau à partir de la phase de réduction, donc nous devons construire un tableau acteurs à l'intérieur du document "valeur" qui est retourné.
Codemap = 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 fait un objet javascript qui contient un tableau. Notez également que map émet la même structure.
exécutez ce qui suit pour exécuter la carte / réduire, la sortie à la collection "pivot" et imprimer le résultat:
printjson (db.acteur.mapReduce (carte, réduire, " pivotent")); DB.pivot.trouver.)(forEach (printjson);
voici le résultat de l'échantillon, notez que "Pretty Woman" et "Runaway" Épouse" ont les deux "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 le fassiez mal. MongoDB jointures "côté client". À peu près comme vous avez dit:
pour le moment, je reçois d'abord les commentaires qui correspondent à mes critères, puis je calcule tous les uid dans ce jeu de résultats, je récupère les objets utilisateur, et je les fusionne avec les résultats du commentaire. On dirait que je suis de faire le mal.
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.
ce n'est pas un" vrai " jointure, mais c'est en fait beaucoup plus utile qu'une jointure SQL parce que vous n'avez pas à vous occuper des lignes dupliquées pour "beaucoup" de jointures latérales, à la place de votre décoration de l'ensemble sélectionné à l'origine.
il y a beaucoup de non-sens et de FUD sur cette page. Il s'avère que 5 ans plus tard MongoDB est toujours une chose.
nous pouvons fusionner deux collections en utilisant mongoDB sub query. Voici l'exemple, Commentss--
`db.commentss.insert([
{ uid:12345, pid:444, comment:"blah" },
{ uid:12345, pid:888, comment:"asdf" },
{ uid:99999, pid:444, comment:"qwer" }])`
Userss --
db.userss.insert([
{ uid:12345, name:"john" },
{ uid:99999, name:"mia" }])
MongoDB sous-requête pour JOINDRE--
`db.commentss.find().forEach(
function (newComments) {
newComments.userss = db.userss.find( { "uid": newComments.uid } ).toArray();
db.newCommentUsers.insert(newComments);
}
);`
recevez le résultat de nouvellement générés Collection--
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"
}
]
}`
Espère que ce sera de l'aide.