306 votes

MongoDB : Combiner les données de plusieurs collections en une seule..comment ?

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.

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.s‌​ave(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

154voto

rmarscher Points 2542

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 :

  • _id
  • premierNom
  • Nom de famille
  • pays
  • genre
  • âge

Et puis le comments a les champs suivants :

  • _id
  • userId
  • commentaire
  • créé

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

1 votes

Je ne le ferais probablement jamais dans un logiciel de production, mais c'est quand même une technique super cool.

3 votes

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/

0 votes

C'est la meilleure façon de procéder. Vous l'avez parfaitement expliqué. Merci.

14voto

Harry Points 186

S'il n'y a pas d'insertion en masse dans mongodb, nous bouclons tous les objets dans le fichier small_collection et les insérer un par un dans le big_collection :

db.small_collection.find().forEach(function(obj){ 
   db.big_collection.insert(obj)
});

1 votes

Db.colleciton.insert([{},{},{}]) L'insertion accepte les tableaux.

2 votes

Cela fonctionne bien pour les petites collections, mais n'oubliez pas de migrer les index :)

1voto

shauli Points 1

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 :

  1. mongodump collection1
  2. collection2.rename(collection1)
  3. mongorestore

Je ne l'ai pas encore essayée, mais elle pourrait être plus rapide que l'approche map/reduce.

-1voto

Vipul Mehta Points 1

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

-3voto

lobster1234 Points 4572

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.com

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.

Powered by:

X