Comment puis-je (dans MongoDB) combiner les données de plusieurs collections en une seule collection ?
Puis-je utiliser map-reduce et si oui, comment ?
Je vous serais reconnaissant de me donner des exemples, car je suis un novice.
Comment puis-je (dans MongoDB) combiner les données de plusieurs collections en une seule collection ?
Puis-je utiliser map-reduce et si oui, comment ?
Je vous serais reconnaissant de me donner des exemples, car je suis un novice.
Bien que vous ne puissiez pas le faire en temps réel, vous pouvez exécuter map-reduce plusieurs fois pour fusionner les données en utilisant l'option de sortie "reduce" dans map/reduce de MongoDB 1.8+ (voir http://www.mongodb.org/display/DOCS/MapReduce#MapReduce-Outputoptions ). Vous devez avoir une clé dans les deux collections que vous pouvez utiliser comme un _id.
Par exemple, disons que vous avez une users
et une comments
et vous voulez avoir une nouvelle collection qui contient des informations démographiques sur les utilisateurs pour chaque commentaire.
Disons que le users
a les champs suivants :
Et puis le comments
a les champs suivants :
Vous feriez ce map/reduce :
var mapUsers, mapComments, reduce;
db.users_comments.remove();
// setup sample data - wouldn't actually use this in production
db.users.remove();
db.comments.remove();
db.users.save({firstName:"Rich",lastName:"S",gender:"M",country:"CA",age:"18"});
db.users.save({firstName:"Rob",lastName:"M",gender:"M",country:"US",age:"25"});
db.users.save({firstName:"Sarah",lastName:"T",gender:"F",country:"US",age:"13"});
var users = db.users.find();
db.comments.save({userId: users[0]._id, "comment": "Hey, what's up?", created: new ISODate()});
db.comments.save({userId: users[1]._id, "comment": "Not much", created: new ISODate()});
db.comments.save({userId: users[0]._id, "comment": "Cool", created: new ISODate()});
// end sample data setup
mapUsers = function() {
var values = {
country: this.country,
gender: this.gender,
age: this.age
};
emit(this._id, values);
};
mapComments = function() {
var values = {
commentId: this._id,
comment: this.comment,
created: this.created
};
emit(this.userId, values);
};
reduce = function(k, values) {
var result = {}, commentFields = {
"commentId": '',
"comment": '',
"created": ''
};
values.forEach(function(value) {
var field;
if ("comment" in value) {
if (!("comments" in result)) {
result.comments = [];
}
result.comments.push(value);
} else if ("comments" in value) {
if (!("comments" in result)) {
result.comments = [];
}
result.comments.push.apply(result.comments, value.comments);
}
for (field in value) {
if (value.hasOwnProperty(field) && !(field in commentFields)) {
result[field] = value[field];
}
}
});
return result;
};
db.users.mapReduce(mapUsers, reduce, {"out": {"reduce": "users_comments"}});
db.comments.mapReduce(mapComments, reduce, {"out": {"reduce": "users_comments"}});
db.users_comments.find().pretty(); // see the resulting collection
A ce stade, vous aurez une nouvelle collection appelée users_comments
qui contient les données fusionnées et vous pouvez maintenant l'utiliser. Ces collections réduites ont toutes _id
qui est la clé que vous émettez dans vos fonctions map et ensuite toutes les valeurs sont un sous-objet à l'intérieur de l'objet value
clé - les valeurs ne sont pas au niveau supérieur de ces documents réduits.
Il s'agit d'un exemple assez simple. Vous pouvez répéter cette opération avec d'autres collections autant que vous le souhaitez pour continuer à développer la collection réduite. Vous pouvez également effectuer des résumés et des agrégations de données au cours du processus. Il est probable que vous définissiez plus d'une fonction de réduction à mesure que la logique d'agrégation et de préservation des champs existants devient plus complexe.
Vous remarquerez également qu'il y a maintenant un document pour chaque utilisateur avec tous les commentaires de cet utilisateur dans un tableau. Si nous fusionnions des données qui ont une relation un-à-un plutôt qu'un-à-plusieurs, elles seraient plates et vous pourriez simplement utiliser une fonction de réduction comme celle-ci :
reduce = function(k, values) {
var result = {};
values.forEach(function(value) {
var field;
for (field in value) {
if (value.hasOwnProperty(field)) {
result[field] = value[field];
}
}
});
return result;
};
Si vous voulez aplatir le users_comments
pour qu'il n'y ait qu'un document par commentaire, en plus de ça :
var map, reduce;
map = function() {
var debug = function(value) {
var field;
for (field in value) {
print(field + ": " + value[field]);
}
};
debug(this);
var that = this;
if ("comments" in this.value) {
this.value.comments.forEach(function(value) {
emit(value.commentId, {
userId: that._id,
country: that.value.country,
age: that.value.age,
comment: value.comment,
created: value.created,
});
});
}
};
reduce = function(k, values) {
var result = {};
values.forEach(function(value) {
var field;
for (field in value) {
if (value.hasOwnProperty(field)) {
result[field] = value[field];
}
}
});
return result;
};
db.users_comments.mapReduce(map, reduce, {"out": "comments_with_demographics"});
Cette technique ne doit absolument pas être exécutée à la volée. Elle est adaptée à une tâche cron ou quelque chose comme ça qui met à jour les données fusionnées périodiquement. Vous voudrez probablement exécuter ensureIndex
sur la nouvelle collection afin de s'assurer que les requêtes que vous effectuez sur celle-ci s'exécutent rapidement (gardez à l'esprit que vos données se trouvent toujours à l'intérieur d'un fichier value
donc si vous indexez comments_with_demographics
sur le commentaire created
temps, il serait db.comments_with_demographics.ensureIndex({"value.created": 1});
Je ne le ferais probablement jamais dans un logiciel de production, mais c'est quand même une technique super cool.
Merci, Dave. J'ai utilisé cette technique pour générer des tableaux d'exportation et de rapport pour un site à fort trafic en production depuis 3 mois sans problème. Voici un autre article qui décrit une utilisation similaire de cette technique : tebros.com/2011/07/
Mongorestore dispose d'une fonction d'ajout par-dessus ce qui se trouve déjà dans la base de données. Ce comportement pourrait donc être utilisé pour combiner deux collections :
Je ne l'ai pas encore essayée, mais elle pourrait être plus rapide que l'approche map/reduce.
Extrait de code. Courtoisie-Multiples posts sur stack overflow dont celui-ci.
db.cust.drop();
db.zip.drop();
db.cust.insert({cust_id:1, zip_id: 101});
db.cust.insert({cust_id:2, zip_id: 101});
db.cust.insert({cust_id:3, zip_id: 101});
db.cust.insert({cust_id:4, zip_id: 102});
db.cust.insert({cust_id:5, zip_id: 102});
db.zip.insert({zip_id:101, zip_cd:'AAA'});
db.zip.insert({zip_id:102, zip_cd:'BBB'});
db.zip.insert({zip_id:103, zip_cd:'CCC'});
mapCust = function() {
var values = {
cust_id: this.cust_id
};
emit(this.zip_id, values);
};
mapZip = function() {
var values = {
zip_cd: this.zip_cd
};
emit(this.zip_id, values);
};
reduceCustZip = function(k, values) {
var result = {};
values.forEach(function(value) {
var field;
if ("cust_id" in value) {
if (!("cust_ids" in result)) {
result.cust_ids = [];
}
result.cust_ids.push(value);
} else {
for (field in value) {
if (value.hasOwnProperty(field) ) {
result[field] = value[field];
}
};
}
});
return result;
};
db.cust_zip.drop();
db.cust.mapReduce(mapCust, reduceCustZip, {"out": {"reduce": "cust_zip"}});
db.zip.mapReduce(mapZip, reduceCustZip, {"out": {"reduce": "cust_zip"}});
db.cust_zip.find();
mapCZ = function() {
var that = this;
if ("cust_ids" in this.value) {
this.value.cust_ids.forEach(function(value) {
emit(value.cust_id, {
zip_id: that._id,
zip_cd: that.value.zip_cd
});
});
}
};
reduceCZ = function(k, values) {
var result = {};
values.forEach(function(value) {
var field;
for (field in value) {
if (value.hasOwnProperty(field)) {
result[field] = value[field];
}
}
});
return result;
};
db.cust_zip_joined.drop();
db.cust_zip.mapReduce(mapCZ, reduceCZ, {"out": "cust_zip_joined"});
db.cust_zip_joined.find().pretty();
var flattenMRCollection=function(dbName,collectionName) {
var collection=db.getSiblingDB(dbName)[collectionName];
var i=0;
var bulk=collection.initializeUnorderedBulkOp();
collection.find({ value: { $exists: true } }).addOption(16).forEach(function(result) {
print((++i));
//collection.update({_id: result._id},result.value);
bulk.find({_id: result._id}).replaceOne(result.value);
if(i%1000==0)
{
print("Executing bulk...");
bulk.execute();
bulk=collection.initializeUnorderedBulkOp();
}
});
bulk.execute();
};
flattenMRCollection("mydb","cust_zip_joined");
db.cust_zip_joined.find().pretty();
Vous devez le faire dans votre couche d'application. Si vous utilisez un ORM, il pourrait utiliser des annotations (ou quelque chose de similaire) pour récupérer les références qui existent dans d'autres collections. Je n'ai travaillé qu'avec Morphia et le @Reference
récupère l'entité référencée lorsqu'elle est interrogée, ce qui me permet d'éviter de le faire moi-même dans le code.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
20 votes
Voulez-vous simplement copier des documents de différentes collections dans une seule collection ou quel est votre plan ? Pouvez-vous préciser "combiner" ? Si vous souhaitez simplement copier via le shell mongo, un fichier
db.collection1.find().forEach(function(doc){db.collection2.save(doc)});
est suffisant. Veuillez préciser le pilote utilisé (java, php, ...) si vous n'utilisez pas le shell mongo.1 votes
J'ai donc une collection (disons les utilisateurs) et d'autres collections comme le carnet d'adresses, la liste des collections de livres, etc. Comment puis-je, en me basant sur la clé user_id, combiner ces collections en une seule et unique collection ? ?
3 votes
En rapport : stackoverflow.com/q/2350495/435605