Question

J'ai une collection plate de documents, où certains documents ont un parent: ObjectId champ, qui pointe vers un autre document de la même collection, soit :

{id: 1, metadata: {text: "I'm a parent"}}
{id: 2, metadata: {text: "I'm child 1", parent: 1}}

Maintenant, j'aimerais récupérer tous les parents où metadata.text = "I'm a parent" en plus ce sont des éléments enfants.Mais je veux que ces données soient dans un format imbriqué, afin de pouvoir simplement les traiter par la suite sans y jeter un œil. metadata.parent.Le résultat devrait ressembler à :

{
  id: 1,
  metadata: {text: "I'm a parent"},
  children: [
    {id: 2, metadata: {text: "I'm child 1", parent: 1}}
  ]
}

(children pourrait également faire partie du metadata objecter si c'est plus simple)

Pourquoi est-ce que je n'enregistre pas les documents dans une structure imbriquée ? Je ne souhaite pas stocker les données dans un format imbriqué dans DB, car ces documents font partie de GridFS.

Le problème principal est : Comment puis-je dire à MongoDB d'imbriquer un document entier?Ou dois-je utiliser le cadre d'agrégation de Mongo pour cette tâche ?

Était-ce utile?

La solution

Pour le type de "projection" que vous demandez, le cadre d'agrégation est le bon outil car ce type de "remodelage de document" n'est vraiment pris en charge que là-bas.

L'autre cas est celui du « parent/enfant », où vous devez encore une fois être « créatif » lors du regroupement à l'aide du cadre d'agrégation.Les opérations complètes montrent de quoi il s’agit essentiellement :

db.collection.aggregate([

    // Group parent and children together with conditionals
    { "$group": {
        "_id": { "$ifNull": [ "$metadata.parent", "$_id" ] },
        "metadata": {
            "$addToSet": {
                "$cond": [
                    { "$ifNull": [ "$metadata.parent", false ] },
                    false,
                    "$metadata"
                ]
            }
        },
        "children": {
            "$push": {
                "$cond": [
                    { "$ifNull": [ "$metadata.parent", false ] },
                    "$$ROOT",
                    false
                ]
            }
        }
    }},

    // Filter out "false" values
    { "$project": {
        "metadata": { "$setDifference": [ "$metadata", [false] ] },
        "children": { "$setDifference": [ "$children", [false] ] }
    }},

    // metadata is an array but should only have one item
    { "$unwind": "$metadata" },

    // This is essentially sorting the children as "sets" are un-ordered 
    { "$unwind": "$children" },
    { "$sort": { "_id": 1, "children._id": 1 } },
    { "$group": {
        "_id": "$_id",
        "metadata": { "$first": "$metadata" },
        "children": { "$push": "$children" }
    }}
])

L'essentiel ici est le $ifNull opérateur utilisé sur le regroupement _id.Celui-ci choisira de $group sur le champ "parent" s'il est présent, sinon en utilisant le document général _id.

Des choses similaires sont faites avec le $cond opérateur plus tard où l'évaluation est faite des données à ajouter au tableau ou à "l'ensemble".Dans ce qui suit $project le false les valeurs sont filtrées à l'aide du $setDifference opérateur.

Si la finale $sort et $group cela semble déroutant, alors la raison réelle est que parce que l'opérateur utilisé est un opérateur "set", le "set" résultant est considéré comme non ordonné.Donc, en réalité, cette partie est juste là pour s'assurer que le contenu du tableau apparaît dans son propre ordre _id champ.

Sans les opérateurs supplémentaires de MongoDB 2.6, cela peut toujours être fait, mais un peu différemment.

db.collection.aggregate([
    { "$group": {
        "_id": { "$ifNull": [ "$metadata.parent", "$_id" ] },
        "metadata": {
            "$addToSet": {
                "$cond": [
                    { "$ifNull": [ "$metadata.parent", false ] },
                    false,
                    "$metadata"
                ]
            }
        },
        "children": {
            "$push": {
                "$cond": [
                    { "$ifNull": [ "$metadata.parent", false ] },
                    { "_id": "$_id","metadata": "$metadata" },
                    false
                ]
            }
        }
    }},
    { "$unwind": "$metadata" },
    { "$match": { "metadata": { "$ne": false } } },
    { "$unwind": "$children" },
    { "$match": { "children": { "$ne": false } } },
    { "$sort": { "_id": 1, "children._id": 1 } },
    { "$group": {
        "_id": "$_id",
        "metadata": { "$first": "$metadata" },
        "children": { "$push": "$children" }
    }}
])

Essentiellement la même chose mais sans les opérateurs les plus récents introduits dans MongoDB 2.6, cela fonctionnerait donc également dans les versions antérieures.

Tout ira bien tant que vos relations sont à un seul niveau parent-enfant.Pour les niveaux imbriqués, vous devrez plutôt appeler un processus mapReduce.

Autres conseils

Je voulais un résultat similaire à la réponse de Neil Lunn, sauf que je voulais récupérer tous les parents, qu'ils aient ou non des enfants.Je voulais également le généraliser pour qu'il fonctionne sur n'importe quelle collection comportant un seul niveau d'enfants imbriqués.

Voici ma requête basée sur la réponse de Neil Lunn

db.collection.aggregate([
  {
    $group: {
      _id: {
        $ifNull: ["$parent", "$_id"]
      },
      parent: {
        $addToSet: {
          $cond: [
            {
              $ifNull: ["$parent", false]
            }, false, "$$ROOT"
          ]
        }
      },
      children: {
        $push: {
          $cond: [
            {
              $ifNull: ["$parent", false]
            }, "$$ROOT", false
          ]
        }
      }
    }
  }, {
    $project: {
      parent: {
        $setDifference: ["$parent", [false]]
      },
      children: {
        $setDifference: ["$children", [false]]
      }
    }
  }, {
    $unwind: "$parent"
  }
])

Cela a pour résultat que chaque parent est renvoyé où le champ parent contient l'intégralité du document parent et le champ enfants renvoie soit un tableau vide si le parent n'a pas d'enfants, soit un tableau de documents enfants.

{
  _id: PARENT_ID
  parent: PARENT_OBJECT
  children: [CHILD_OBJECTS]
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top