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