Frage

Ich habe eine flache Sammlung von Dokumenten, wobei einige Dokumente eine haben parent: ObjectId feld, das auf ein anderes Dokument aus derselben Sammlung verweist, z.:

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

Jetzt möchte ich abrufen alle Eltern wo metadata.text = "I'm a parent" außerdem sind es untergeordnete Elemente.Aber ich möchte diese Daten in einem verschachtelten Format, damit ich sie danach einfach verarbeiten kann, ohne einen Blick darauf zu werfen metadata.parent.Die Ausgabe sollte so aussehen:

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

(children könnte auch Teil der Eltern sein metadata objekt, wenn das einfacher ist)

Warum speichere ich die Dokumente nicht in einer verschachtelten Struktur? Ich möchte die Daten nicht in einem verschachtelten Format in DB speichern, da diese Dokumente Teil von GridFS sind.

Das Hauptproblem ist: Wie kann ich MongoDB anweisen, ein ganzes Dokument zu verschachteln?Oder muss ich für diese Aufgabe das Aggregationsframework von Mongo verwenden?

War es hilfreich?

Lösung

Für die Art von "Projektion", nach der Sie fragen, ist das Aggregationsframework das richtige Werkzeug, da diese Art der "Dokumentenumgestaltung" nur dort wirklich unterstützt wird.

Der andere Fall ist die Sache "Eltern / Kind", bei der Sie beim Gruppieren mithilfe des Aggregationsframeworks erneut "kreativ" sein müssen.Die vollständigen Operationen zeigen, worum es im Wesentlichen geht:

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

Die Hauptsache hier ist die $ifNull operator, der für die Gruppierung verwendet wird _id.Dies wird wählen $group im Feld "übergeordnetes Element", sofern vorhanden, andernfalls unter Verwendung des allgemeinen Dokuments _id.

Ähnliches wird mit dem gemacht $cond operator später, wo die Auswertung vorgenommen wird, welche Daten dem Array hinzugefügt oder "gesetzt" werden sollen.Nachfolgend $project der false werte werden mit Hilfe des Filters herausgefiltert $setDifference Betreiber.

Wenn das Finale $sort und $group es scheint verwirrend, dann liegt der eigentliche Grund darin, dass der verwendete Operator ein "Mengen" -Operator ist, der resultierende "Satz" wird als ungeordnet betrachtet.Dieser Teil ist also wirklich nur dazu da, um sicherzustellen, dass der Array-Inhalt in der eigenen Reihenfolge angezeigt wird _id Feld.

Ohne die zusätzlichen Operatoren von MongoDB 2.6 kann dies immer noch gemacht werden, aber nur ein bisschen anders.

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

Im Wesentlichen das Gleiche, aber ohne die neueren Operatoren, die in MongoDB 2.6 eingeführt wurden, würde dies auch in früheren Versionen funktionieren.

Dies alles wird in Ordnung sein, solange Ihre Beziehungen eine einzige Ebene von Eltern und Kind sind.Für verschachtelte Ebenen müssten Sie stattdessen einen MapReduce-Prozess aufrufen.

Andere Tipps

Ich wollte ein ähnliches Ergebnis wie Neil Lunns Antwort, außer dass ich alle Eltern holen wollte, unabhängig davon, ob sie Kinder haben oder nicht.Ich wollte es auch so verallgemeinern, dass es für jede Sammlung mit einer einzigen Ebene verschachtelter Kinder funktioniert.

Hier ist meine Anfrage basierend auf Neil Lunns Antwort

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

Dies führt dazu, dass jedes übergeordnete Element zurückgegeben wird, wobei das übergeordnete Feld das gesamte übergeordnete Dokument enthält und das untergeordnete Feld entweder ein leeres Array zurückgibt, wenn das übergeordnete Element keine untergeordneten Elemente hat, oder ein Array von untergeordneten Dokumenten.

{
  _id: PARENT_ID
  parent: PARENT_OBJECT
  children: [CHILD_OBJECTS]
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top