web-dev-qa-db-fra.com

Mongodb, requête agrégée avec $ lookup

Vous avez deux collections, des tags et des personnes. 

tags modèle:

{
  en: String,
  sv: String
}

modèle de personne:

{
  name: String,
  projects: [
    title: String,
    tags: [
      {
        type: Schema.ObjectId,
        ref: 'tag'
      }
    ]
  ]

}

Je veux une requête qui renvoie toutes les balises utilisées dans le modèle de personne. Tous les documents.

Sometehing comme

var query = mongoose.model('tag').find({...});

Ou devrais-je en quelque sorte utiliser l'approche globale à cela? 

8
Joe

Pour un document particulier, vous pouvez utiliser la fonction populate() comme 

var query = mongoose.model("person").find({ "name": "foo" }).populate("projects.tags");

Et si vous souhaitez rechercher des personnes portant une balise avec «MongoDB» ou «Node JS», par exemple, vous pouvez inclure l'option de requête dans la surcharge de fonction populate() en tant que:

var query = mongoose.model("person").find({ "name": "foo" }).populate({
    "path": "projects.tags",
    "match": { "en": { "$in": ["MongoDB", "Node JS"] } }
});

Si vous voulez que toutes les balises existant dans "project.tags" pour toutes les personnes, le cadre d'agrégation est la voie à suivre. Pensez à exécuter ce pipeline sur la collection de personnes et utilisez l'opérateur $lookup pour effectuer une jointure à gauche sur la collection de balises:

mongoose.model('person').aggregate([
    { "$unwind": "$projects" },
    { "$unwind": "$projects.tags" },
    {
        "$lookup": {
            "from": "tags",
            "localField": "projects.tags",
            "foreignField": "_id",
            "as": "resultingTagsArray"
        }
    },
    { "$unwind": "$resultingTagsArray" },
    {
        "$group": {
            "_id": null,
            "allTags": { "$addToSet": "$resultingTagsArray" },
            "count": { "$sum": 1 }
        }
    }
 ]).exec(function(err, results){
    console.log(results);
 })

Pour une personne donnée, appliquez ensuite un pipeline $match comme première étape pour filtrer les documents: 

mongoose.model('person').aggregate([
    { "$match": { "name": "foo" } },
    { "$unwind": "$projects" },
    { "$unwind": "$projects.tags" },
    {
        "$lookup": {
            "from": "tags",
            "localField": "projects.tags",
            "foreignField": "_id",
            "as": "resultingTagsArray"
        }
    },
    { "$unwind": "$resultingTagsArray" },
    {
        "$group": {
            "_id": null,
            "allTags": { "$addToSet": "$resultingTagsArray" },
            "count": { "$sum": 1 }
        }
    }
 ]).exec(function(err, results){
    console.log(results);
 })

Une autre solution de contournement si vous utilisez des versions de MongoDB> = 2.6 ou <= 3.0 qui ne prennent pas en charge l'opérateur $lookup consiste à renseigner les résultats de l'agrégation de la manière suivante:

mongoose.model('person').aggregate([
    { "$unwind": "$projects" },
    { "$unwind": "$projects.tags" },    
    {
        "$group": {
            "_id": null,
            "allTags": { "$addToSet": "$projects.tags" }
        }
    }
 ], function(err, result) {
    mongoose.model('person')
    .populate(result, { "path": "allTags" }, function(err, results) {
        if (err) throw err;
        console.log(JSON.stringify(results, undefined, 4 ));
    });
});
27
chridam

Si vous utilisez MongoDb version 3.2, vous pouvez utiliser $ lookup qui effectue une jointure externe gauche.

1
Clement Amarnath