Domanda

Ho una raccolta piatta di documenti, in cui alcuni documenti hanno un campo parent: ObjectId, che punta un altro documento dalla stessa collezione, I.e.:

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

Ora vorrei recuperare tutti i genitori dove metadata.text = "I'm a parent" Plus It's Child Elements .Ma voglio che i dati in un formato nidificato, così posso semplicemente elaborarlo in seguito senza dare un'occhiata a metadata.parent.L'output dovrebbe essere simile a:

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

(children potrebbe anche essere parte dell'oggetto metadata del genitore se è più facile)

Perché non salvare i documenti in una struttura annidata? non voglio archiviare i dati in un formato nidificato in DB, poiché questi documenti fanno parte di GRIDFS.

Il problema principale è: Come posso dire a mongodb di nidificare un intero documento ?Oppure devo usare il framework di aggregazione del mongo per quel compito?

È stato utile?

Soluzione

Per il tipo di "proiezione" che stai chiedendo, allora il framework di aggregazione è lo strumento corretto come questo tipo di "documento ri-shaping" è solo supportato lì.

L'altro caso è la cosa "genitore / figlio", in cui hai ancora bisogno di essere "creativo" durante il raggruppamento utilizzando il quadro di aggregazione. Le operazioni complete mostrano ciò che è essenzialmente coinvolto:

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" }
    }}
])
.

La cosa principale qui è il $ifNull < / A> Operatore utilizzato sul gruppo _id. Questo sceglierà di $group sul Campo "Parent" dove presente, altrimenti utilizzando il documento generale _id.

Le cose simili vengono eseguite con il $cond < / A> Operatore in un secondo momento in cui è fatta la valutazione con cui i dati da aggiungere all'array o "Set". In quanto segue $project I valori false sono filtrati dall'uso del $setDifference Operatore.

Se l'ultimo $sort e $group Sembrano confuso, quindi Il motivo attuale è dovuto all'operatore utilizzato è un operatore "impostato" che il "set impostato" risultante è considerato non ordinato. Quindi, in realtà, quella parte è solo lì per assicurarsi che il contenuto dell'array appaia in ordine del proprio campo _id.

Senza gli operatori aggiuntivi di Mongodb 2.6 Questo può ancora essere fatto, ma solo un po 'in modo diverso.

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" }
    }}
])
.

Essenzialmente la stessa cosa ma senza gli operatori più recenti introdotti a Mongodb 2.6, quindi questo funzionerebbe anche nelle versioni precedenti.

Questo andrà tutto bene finché le tue relazioni sono un singolo livello di genitore e figlio. Per i livelli nidificati è necessario richiamare invece un processo di Mapreduce.

Altri suggerimenti

Volevo un risultato simile alla risposta di Neil Lunn, tranne che volevo recuperare tutti i genitori indipendentemente da loro che hanno figli o meno.Volevo anche generalizzarlo per lavorare su qualsiasi collezione che aveva un unico livello di bambini annidati.

Ecco la mia query basata sulla risposta di 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"
  }
])
.

Ciò si traduce in ogni genitore restituito dove il campo principale contiene l'intero documento genitore e il campo dei bambini che restituisce un array vuoto se il genitore non ha figli o una serie di documenti figlio.

{
  _id: PARENT_ID
  parent: PARENT_OBJECT
  children: [CHILD_OBJECTS]
}
.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top