javascript mongodb mongodb-query aggregation-framework

javascript - Filtro de agregación después de $ lookup



mongodb mongodb-query (1)

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

MongoDB 3.6 - Sub-tubería

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 + $ desenrollar + $ combinación de coincidencias

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 se pregunta por qué $unwind en lugar de usar el $filter en la matriz, lea la búsqueda agregada de $ El tamaño total de los documentos en la tubería coincidente excede el tamaño máximo del documento para todos los detalles sobre por qué esto es generalmente necesario y mucho más óptimo.

Para los lanzamientos de MongoDB 3.6 y posteriores, la "sub-tubería" más expresiva es generalmente lo que desea "filtrar" los resultados de la colección extranjera antes de que cualquier cosa vuelva a la matriz.

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

Original

Usar $lookup como este no es la forma más "eficiente" de hacer lo que quiere aquí. Pero más sobre esto más tarde.

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 use $redact en $redact lugar:

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 sí mismo no puede "todavía" consultar para seleccionar solo ciertos datos. Por lo tanto, todo "filtrado" debe ocurrir después de la $lookup

Pero realmente para este tipo de "autounión", es mejor no utilizar la $lookup y evitar la sobrecarga de una lectura adicional y una "fusión hash" por completo. Simplemente busque los elementos relacionados y $group lugar:

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

Lo que solo sale un poco diferente porque eliminé deliberadamente los campos extraños. Añádelos en ti 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 resultado null de la matriz, creado cuando el documento actual era el parent en el procesamiento de elementos 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 "elementos secundarios" 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 realmente no se está "reduciendo" el 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 el padre como el hijo. Los datos devueltos son los mismos, y todo lo que está haciendo en el servidor o 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 punto del ejemplo de documentación "Estructuras de árbol modelo con referencias de niños" en su estructura, donde facilita la selección de padres e hijos dentro de una consulta.

¿Cómo puedo agregar un filtro después de una búsqueda $ o hay algún otro método para hacerlo?

Mi prueba de recopilación de datos es:

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

Selecciono id 100 y agrego los niños:

db.test.aggregate([ { $match : { id: 100 } }, { $lookup : { from : "test", localField : "id", foreignField : "contain", as : "childs" } }]);

Vuelvo:

{ "_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 ] }, { "_id":ObjectId("570557d4094a4514fc1291d9"), "id":121, "value":"2", "contain":[ 100, 120 ] } ] }

Pero solo quiero niños que coincidan con "valor: 1"

Al final espero este 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 ] } ] }