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

Filtro de agregación después de $búsqueda

La pregunta aquí es en realidad sobre algo diferente y no necesita $lookup en absoluto. Pero para cualquiera que llegue aquí simplemente por el título de "filtrado después de $búsqueda", estas son las técnicas para usted:

MongoDB 3.6 - Canal secundario

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

Anteriormente:$búsqueda + $desenrollado + $coincidencia coalescencia

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

Si te preguntas por qué $unwind en lugar de usar $filter en la matriz, luego lea Aggregate $lookup El tamaño total de los documentos en la canalización coincidente excede el tamaño máximo del documento para obtener todos los detalles sobre por qué esto es generalmente necesario y mucho más óptimo.

Para las versiones de MongoDB 3.6 y posteriores, la "canalización secundaria" más expresiva es generalmente lo que desea "filtrar" los resultados de la colección externa antes de que se devuelva algo a la matriz.

Sin embargo, volvamos a la respuesta que en realidad describe por qué la pregunta no necesita "no unirse" en absoluto...

Originales

Usando $lookup así no es la forma más "eficiente" de hacer lo que quieres aquí. Pero más sobre esto más adelante.

Como concepto básico, solo use $filter en la matriz resultante:

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

O usa $redact en cambio:

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

Ambos obtienen el mismo resultado:

{  
  "_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 conclusión es que $lookup en sí mismo no puede consultar "todavía" para seleccionar solo ciertos datos. Entonces, todo el "filtrado" debe ocurrir después de $lookup

Pero realmente para este tipo de "auto-unión" es mejor no usar $lookup en absoluto y evitando la sobrecarga de una lectura adicional y "hash-merge" por completo. Solo busque los artículos relacionados y $group en cambio:

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

Que solo sale un poco diferente porque eliminé deliberadamente los campos extraños. Agrégalos tú mismo si realmente quieres:

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

Entonces, el único problema real aquí es "filtrar" cualquier null resultado de la matriz, creada cuando el documento actual era el parent en el procesamiento de artículos para $push .

Lo que también parece faltar aquí es que el resultado que está buscando no necesita agregación o "subconsultas" en absoluto. La estructura que ha concluido o posiblemente encontrado en otro lugar está "diseñada" para que pueda obtener un "nodo" y todos sus "hijos" en una sola solicitud de consulta.

Eso significa que solo la "consulta" es todo lo que realmente se necesita, y la recopilación de datos (que es todo lo que está sucediendo ya que no se está "reduciendo" ningún contenido) es solo una función de iterar el resultado del cursor:

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);

Esto hace exactamente lo mismo:

{
  "_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
      ]
    }
  ]
}

Y sirve como prueba de que todo lo que realmente necesita hacer aquí es emitir la consulta "única" para seleccionar tanto al padre como a los hijos. Los datos devueltos son los mismos, y todo lo que está haciendo en el servidor o en el cliente es "masajear" en otro formato recopilado.

Este es uno de esos casos en los que puede quedar "atrapado" pensando en cómo hizo las cosas en una base de datos "relacional" y no darse cuenta de que, dado que la forma en que se almacenan los datos ha "cambiado", ya no necesita usar el mismo enfoque.

Ese es exactamente el objetivo del ejemplo de documentación "Estructuras de árbol modelo con referencias secundarias" en su estructura, donde facilita la selección de padres e hijos dentro de una consulta.