MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Proiezione MongoDB di array nidificati

Aggiornamento 2017

Una domanda così ben posta merita una risposta moderna. Il tipo di filtraggio dell'array richiesto può effettivamente essere eseguito nelle moderne versioni di MongoDB dopo la 3.2 semplicemente tramite $match e $project fasi della pipeline, proprio come intende l'operazione di query semplice originale.

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

Questo fa uso di $filter e $map gli operatori restituiscono solo gli elementi dagli array come soddisferebbe le condizioni, ed è molto meglio per le prestazioni rispetto all'utilizzo di $unwind . Poiché le fasi della pipeline rispecchiano efficacemente la struttura di "query" e "project" da un .find() operazione, le prestazioni qui sono sostanzialmente alla pari con tali operazioni.

Nota che dove l'intenzione è di lavorare effettivamente "attraverso i documenti" per riunire i dettagli da documenti "multipli" piuttosto che da "uno", questo di solito richiederebbe un qualche tipo di $unwind operazione per fare ciò, consentendo in tal modo agli elementi dell'array di essere accessibili per il "raggruppamento".

Questo è fondamentalmente l'approccio:

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

Questo è il "filtro di array" su più di una singola corrispondenza che le capacità di proiezione di base di .find() non posso fare.

Hai array "nidificati" quindi devi elaborare $unwind due volte. Insieme alle altre operazioni.