MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

MongoDB-Projektion verschachtelter Arrays

Aktualisierung 2017

Eine so gut gestellte Frage verdient eine moderne Antwort. Die Art der angeforderten Array-Filterung kann tatsächlich in modernen MongoDB-Versionen nach 3.2 einfach durch $match durchgeführt werden und $project Pipeline-Stufen, ähnlich wie es die ursprüngliche einfache Abfrageoperation beabsichtigt.

db.accounts.aggregate([
  { "$match": {
    "email" : "[email protected]",
    "groups": {
      "$elemMatch": { 
        "name": "group1",
        "contacts.localId": { "$in": [ "c1","c3", null ] }
      }
    }
  }},
  { "$addFields": {
    "groups": {
      "$filter": {
        "input": {
          "$map": {
            "input": "$groups",
            "as": "g",
            "in": {
              "name": "$$g.name",
              "contacts": {
                "$filter": {
                  "input": "$$g.contacts",
                  "as": "c",
                  "cond": {
                    "$or": [
                      { "$eq": [ "$$c.localId", "c1" ] },
                      { "$eq": [ "$$c.localId", "c3" ] }
                    ]
                  } 
                }
              }
            }
          }
        },
        "as": "g",
        "cond": {
          "$and": [
            { "$eq": [ "$$g.name", "group1" ] },
            { "$gt": [ { "$size": "$$g.contacts" }, 0 ] }
          ]
        }
      }
    }
  }}
])

Dabei wird der $filter verwendet und $map Operatoren, nur die Elemente aus den Arrays zurückzugeben, die die Bedingungen erfüllen würden, und ist für die Leistung weitaus besser als die Verwendung von $unwind . Da die Pipeline-Stufen effektiv die Struktur von „query“ und „project“ aus einem .find() widerspiegeln Betrieb ist die Leistung hier im Wesentlichen auf Augenhöhe mit einem solchen und Betrieb.

Beachten Sie, dass dort, wo die Absicht ist, tatsächlich "dokumentenübergreifend" zu arbeiten um Details aus "mehreren" Dokumenten statt aus "einem" zusammenzuführen, würde dies normalerweise eine Art von $unwind erfordern Operation, um dies zu ermöglichen, damit die Array-Elemente für "Gruppierung" zugänglich sind.

Dies ist im Grunde der Ansatz:

db.accounts.aggregate([
    // Match the documents by query
    { "$match": {
        "email" : "[email protected]",
        "groups.name": "group1",
        "groups.contacts.localId": { "$in": [ "c1","c3", null ] },
    }},

    // De-normalize nested array
    { "$unwind": "$groups" },
    { "$unwind": "$groups.contacts" },

    // Filter the actual array elements as desired
    { "$match": {
        "groups.name": "group1",
        "groups.contacts.localId": { "$in": [ "c1","c3", null ] },
    }},

    // Group the intermediate result.
    { "$group": {
        "_id": { "email": "$email", "name": "$groups.name" },
        "contacts": { "$push": "$groups.contacts" }
    }},

    // Group the final result
    { "$group": {
        "_id": "$_id.email",
        "groups": { "$push": {
            "name": "$_id.name",
            "contacts": "$contacts" 
        }}
    }}
])

Dies ist eine "Array-Filterung" bei mehr als einer einzelnen Übereinstimmung mit den grundlegenden Projektionsfunktionen von .find() kann nicht.

Sie haben "verschachtelte" Arrays, daher müssen Sie $unwind verarbeiten zweimal. Zusammen mit den anderen Operationen.