MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Projection MongoDB de tableaux imbriqués

Mise à jour 2017

Une question aussi bien posée mérite une réponse moderne. Le type de filtrage de tableau demandé peut en fait être effectué dans les versions modernes de MongoDB post 3.2 via simplement $match et $project étapes du pipeline, un peu comme l'opération de requête simple d'origine le prévoit.

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

Cela utilise le $filter et $map les opérateurs ne renvoient que les éléments des tableaux conformément aux conditions, et sont bien meilleurs pour les performances que d'utiliser $unwind . Étant donné que les étapes du pipeline reflètent efficacement la structure de "query" et "project" à partir d'un .find() opération, les performances ici sont fondamentalement à égalité avec une telle opération.

Notez que lorsque l'intention est de travailler réellement "sur plusieurs documents" pour rassembler les détails de "plusieurs" documents plutôt que "d'un seul", cela nécessiterait généralement un certain type de $unwind pour ce faire, permettant ainsi aux éléments du tableau d'être accessibles pour le "regroupement".

Voici essentiellement l'approche :

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

Il s'agit d'un "filtrage de tableau" sur plus d'une seule correspondance avec les capacités de projection de base de .find() ne peut pas faire.

Vous avez des tableaux "imbriqués", vous devez donc traiter $unwind à deux reprises. Avec les autres opérations.