MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Projeção MongoDB de Arrays Aninhados

Atualização de 2017


Uma pergunta tão bem colocada merece uma resposta moderna. O tipo de filtragem de array solicitado pode realmente ser feito nas versões modernas do MongoDB pós 3.2 através de simplesmente $match e $project estágios de pipeline, bem como a operação de consulta simples original pretende.
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 ] }
          ]
        }
      }
    }
  }}
])

Isso faz uso do $filter e $map operadores para retornar apenas os elementos dos arrays conforme as condições, e é muito melhor para o desempenho do que usar $unwind . Como os estágios do pipeline refletem efetivamente a estrutura de "consulta" e "projeto" de um .find() operação, o desempenho aqui é basicamente a par com tal e operação.

Observe que onde a intenção é realmente trabalhar "entre documentos" para reunir detalhes de "vários" documentos em vez de "um", isso normalmente exigiria algum tipo de $unwind operação para fazê-lo, permitindo assim que os itens da matriz sejam acessíveis para "agrupamento".

Esta é basicamente a abordagem:
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" 
        }}
    }}
])

Isso é "filtragem de matriz" em mais de uma única correspondência que os recursos básicos de projeção de .find() Não pode fazer.

Você tem arrays "aninhados", portanto, você precisa processar $unwind duas vezes. Junto com as outras operações.