sql >> Base de Datos >  >> NoSQL >> MongoDB

MongoDB Proyección de matrices anidadas

Actualización de 2017

Una pregunta tan bien planteada merece una respuesta moderna. El tipo de filtrado de matrices solicitado se puede realizar en las versiones modernas de MongoDB posteriores a la 3.2 mediante simplemente $match y $project etapas de canalización, muy similar a la intención de la operación de consulta simple original.

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

Esto hace uso del $filter y $map operadores para devolver solo los elementos de las matrices que cumplirían las condiciones, y es mucho mejor para el rendimiento que usar $unwind . Dado que las etapas de canalización reflejan efectivamente la estructura de "consulta" y "proyecto" de un .find() operación, el rendimiento aquí es básicamente a la par con tal operación.

Tenga en cuenta que donde la intención es trabajar realmente "a través de documentos" para reunir detalles de "múltiples" documentos en lugar de "uno", entonces esto generalmente requeriría algún tipo de $unwind operación para hacerlo, como tal, permitiendo que los elementos de la matriz sean accesibles para "agrupar".

Este es básicamente el enfoque:

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

Esto es "filtrado de matriz" en más de una sola coincidencia que las capacidades básicas de proyección de .find() no puede hacer.

Tiene matrices "anidadas", por lo que necesita procesar $unwind dos veces. Junto con las demás operaciones.