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

Filtro di aggregazione dopo $lookup

La domanda qui riguarda in realtà qualcosa di diverso e non ha bisogno di $lookup affatto. Ma per chiunque arrivi qui esclusivamente dal titolo di "filtraggio dopo $lookup", queste sono le tecniche per te:

MongoDB 3.6 - Sottopipeline

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
      "from": "test",
      "let": { "id": "$id" },
      "pipeline": [
        { "$match": {
          "value": "1",
          "$expr": { "$in": [ "$$id", "$contain" ] }
        }}
      ],
      "as": "childs"
    }}
])

Precedente - $lookup + $unwind + $match coalescence

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$unwind": "$childs" },
    { "$match": { "childs.value": "1" } },
    { "$group": {
        "_id": "$_id",
        "id": { "$first": "$id" },
        "value": { "$first": "$value" },
        "contain": { "$first": "$contain" },
        "childs": { "$push": "$childs" }
     }}
])

Se ti chiedi perché dovresti $unwind invece di usare $filter sull'array, quindi leggi Aggregate $lookup La dimensione totale dei documenti nella pipeline corrispondente supera la dimensione massima del documento per tutti i dettagli sul motivo per cui questo è generalmente necessario e molto più ottimale.

Per le versioni di MongoDB 3.6 e successive, la "sotto-pipeline" più espressiva è generalmente quella che vuoi "filtrare" i risultati della raccolta straniera prima che qualcosa venga restituito nell'array.

Torniamo alla risposta, che in realtà descrive perché la domanda posta non ha bisogno di "nessuna partecipazione"....

Originale

Usando $lookup come questo non è il modo più "efficiente" per fare quello che vuoi qui. Ma ne parleremo più avanti.

Come concetto di base, usa semplicemente $filter sull'array risultante:

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$project": {
        "id": 1,
        "value": 1,
        "contain": 1,
        "childs": {
           "$filter": {
               "input": "$childs",
               "as": "child",
               "cond": { "$eq": [ "$$child.value", "1" ] }
           }
        }
    }}
]);

Oppure usa $redact invece:

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$redact": {
        "$cond": {
           "if": {
              "$or": [
                { "$eq": [ "$value", "0" ] },
                { "$eq": [ "$value", "1" ] }
              ]
           },
           "then": "$$DESCEND",
           "else": "$$PRUNE"
        }
    }}
]);

Entrambi ottengono lo stesso risultato:

{  
  "_id":ObjectId("570557d4094a4514fc1291d6"),
  "id":100,
  "value":"0",
  "contain":[ ],
  "childs":[ {  
      "_id":ObjectId("570557d4094a4514fc1291d7"),
      "id":110,
      "value":"1",
      "contain":[ 100 ]
    },
    {  
      "_id":ObjectId("570557d4094a4514fc1291d8"),
      "id":120,
      "value":"1",
      "contain":[ 100 ]
    }
  ]
}

La conclusione è che $lookup di per sé non può "ancora" interrogare per selezionare solo determinati dati. Quindi tutti i "filtri" devono essere eseguiti dopo il $lookup

Ma davvero per questo tipo di "partecipazione automatica" è meglio non usare $lookup del tutto ed evitando il sovraccarico di una lettura aggiuntiva e "unione hash" del tutto. Basta recuperare gli elementi correlati e $group invece:

db.test.aggregate([
  { "$match": { 
    "$or": [
      { "id": 100 },
      { "contain.0": 100, "value": "1" }
    ]
  }},
  { "$group": {
    "_id": {
      "$cond": {
        "if": { "$eq": [ "$value", "0" ] },
        "then": "$id",
        "else": { "$arrayElemAt": [ "$contain", 0 ] }
      }
    },
    "value": { "$first": { "$literal": "0"} },
    "childs": {
      "$push": {
        "$cond": {
          "if": { "$ne": [ "$value", "0" ] },
          "then": "$$ROOT",
          "else": null
        }
      }
    }
  }},
  { "$project": {
    "value": 1,
    "childs": {
      "$filter": {
        "input": "$childs",
        "as": "child",
        "cond": { "$ne": [ "$$child", null ] }
      }
    }
  }}
])

Il che risulta solo leggermente diverso perché ho deliberatamente rimosso i campi estranei. Aggiungili tu stesso se lo desideri davvero:

{
  "_id" : 100,
  "value" : "0",
  "childs" : [
    {
      "_id" : ObjectId("570557d4094a4514fc1291d7"),
      "id" : 110,
      "value" : "1",
      "contain" : [ 100 ]
    },
    {
      "_id" : ObjectId("570557d4094a4514fc1291d8"),
      "id" : 120,
      "value" : "1",
      "contain" : [ 100 ]
    }
  ]
}

Quindi l'unico vero problema qui è "filtrare" qualsiasi null risultato dell'array, creato quando il documento corrente era il parent nell'elaborazione di elementi in $push .

Quello che sembra anche che manchi qui è che il risultato che stai cercando non ha bisogno di aggregazioni o "sottoquery". La struttura che hai concluso o eventualmente trovato altrove è "progettata" in modo da poter ottenere un "nodo" e tutti i suoi "figli" in un'unica richiesta di query.

Ciò significa che solo la "query" è tutto ciò che è veramente necessario e la raccolta dei dati (che è tutto ciò che sta accadendo poiché nessun contenuto viene effettivamente "ridotto") è solo una funzione dell'iterazione del risultato del cursore:

var result = {};

db.test.find({
  "$or": [
    { "id": 100 },
    { "contain.0": 100, "value": "1" }
  ]
}).sort({ "contain.0": 1 }).forEach(function(doc) {
  if ( doc.id == 100 ) {
    result = doc;
    result.childs = []
  } else {
    result.childs.push(doc)
  }
})

printjson(result);

Questo fa esattamente la stessa cosa:

{
  "_id" : ObjectId("570557d4094a4514fc1291d6"),
  "id" : 100,
  "value" : "0",
  "contain" : [ ],
  "childs" : [
    {
      "_id" : ObjectId("570557d4094a4514fc1291d7"),
      "id" : 110,
      "value" : "1",
      "contain" : [
              100
      ]
    },
    {
      "_id" : ObjectId("570557d4094a4514fc1291d8"),
      "id" : 120,
      "value" : "1",
      "contain" : [
              100
      ]
    }
  ]
}

E serve come prova che tutto ciò che devi davvero fare qui è emettere la query "singola" per selezionare sia il genitore che i figli. I dati restituiti sono gli stessi e tutto ciò che stai facendo sul server o sul client è "massaggiare" in un altro formato raccolto.

Questo è uno di quei casi in cui puoi rimanere "preso" nel pensare a come hai fatto le cose in un database "relazionale" e non renderti conto che poiché il modo in cui i dati sono archiviati è "cambiato", non è più necessario utilizzare lo stesso approccio.

Questo è esattamente il punto dell'esempio di documentazione "Strutture ad albero del modello con riferimenti figlio" nella sua struttura, dove semplifica la selezione di genitori e figli all'interno di una query.