Anotación para aquellos que buscan - Recuento extranjero
Un poco mejor de lo que se respondió originalmente es usar la forma más nueva de $lookup
de MongoDB 3.6. En realidad, esto puede hacer el "conteo" dentro de la expresión "subcanalización" en lugar de devolver una "matriz" para el filtrado y el conteo posteriores o incluso usar $unwind
db.emailGroup.aggregate([
{ "$lookup": {
"from": "link",
"let": { "id": "$_id" },
"pipeline": [
{ "$match": {
"originalLink": "",
"$expr": { "$eq": [ "$$id", "$_id" ] }
}},
{ "$count": "count" }
],
"as": "linkCount"
}},
{ "$addFields": {
"linkCount": { "$sum": "$linkCount.count" }
}}
])
No es lo que pedía la pregunta original, sino parte de la respuesta a continuación en la forma ahora más óptima, como por supuesto el resultado de $lookup
se reduce al "recuento coincidente" únicamente en lugar de "todos los documentos coincidentes".
Originales
La forma correcta de hacer esto sería agregar el "linkCount"
al $group
escenario así como un $first
en cualquier campo adicional del documento principal para obtener el formulario "singular" como era el estado "antes" del $unwind
se procesó en la matriz que fue el resultado de $lookup
:
Todos los detalles
db.emailGroup.aggregate([
{ "$lookup": {
"from": "link",
"localField": "_id",
"foreignField": "emailGroupId",
"as": "link"
}},
{ "$unwind": "$link" },
{ "$match": { "link.originalLink": "" } },
{ "$group": {
"_id": "$_id",
"partId": { "$first": "$partId" },
"link": { "$push": "$link" },
"linkCount": {
"$sum": {
"$size": {
"$ifNull": [ "$link.linkHistory", [] ]
}
}
}
}}
])
Produce:
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
"link" : [
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf")
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca")
}
]
}
],
"linkCount" : 2
}
Agrupar por ID de pieza
db.emailGroup.aggregate([
{ "$lookup": {
"from": "link",
"localField": "_id",
"foreignField": "emailGroupId",
"as": "link"
}},
{ "$unwind": "$link" },
{ "$match": { "link.originalLink": "" } },
{ "$group": {
"_id": "$partId",
"linkCount": {
"$sum": {
"$size": {
"$ifNull": [ "$link.linkHistory", [] ]
}
}
}
}}
])
Produce
{
"_id" : "f56c7c71eb14a20e6129a667872f9c4f",
"linkCount" : 2
}
La razón por la que lo hace de esta manera con un $unwind
y luego un $match
se debe a cómo MongoDB realmente maneja la canalización cuando se emite en ese orden. Esto es lo que sucede con $lookup
como se demuestra en el "explain"
salida de la operación:
{
"$lookup" : {
"from" : "link",
"as" : "link",
"localField" : "_id",
"foreignField" : "emailGroupId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"originalLink" : {
"$eq" : ""
}
}
}
},
{
"$group" : {
Dejo la parte con $group
en esa salida para demostrar que las otras dos etapas de canalización "desaparecen". Esto se debe a que se han "enrollado" en el $lookup
etapa de tubería como se muestra. De hecho, así es como MongoDB trata la posibilidad de que el límite de BSON pueda ser excedido por el resultado de "unir" los resultados de $lookup
en una matriz del documento principal.
Alternativamente, puede escribir la operación de esta manera:
Todos los detalles
db.emailGroup.aggregate([
{ "$lookup": {
"from": "link",
"localField": "_id",
"foreignField": "emailGroupId",
"as": "link"
}},
{ "$addFields": {
"link": {
"$filter": {
"input": "$link",
"as": "l",
"cond": { "$eq": [ "$$l.originalLink", "" ] }
}
},
"linkCount": {
"$sum": {
"$map": {
"input": {
"$filter": {
"input": "$link",
"as": "l",
"cond": { "$eq": [ "$$l.originalLink", "" ] }
}
},
"as": "l",
"in": { "$size": { "$ifNull": [ "$$l.linkHistory", [] ] } }
}
}
}
}}
])
Agrupar por partId
db.emailGroup.aggregate([
{ "$lookup": {
"from": "link",
"localField": "_id",
"foreignField": "emailGroupId",
"as": "link"
}},
{ "$addFields": {
"link": {
"$filter": {
"input": "$link",
"as": "l",
"cond": { "$eq": [ "$$l.originalLink", "" ] }
}
},
"linkCount": {
"$sum": {
"$map": {
"input": {
"$filter": {
"input": "$link",
"as": "l",
"cond": { "$eq": [ "$$l.originalLink", "" ] }
}
},
"as": "l",
"in": { "$size": { "$ifNull": [ "$$l.linkHistory", [] ] } }
}
}
}
}},
{ "$unwind": "$link" },
{ "$group": {
"_id": "$partId",
"linkCount": { "$sum": "$linkCount" }
}}
])
Que tiene el mismo resultado pero "difiere" de la primera consulta en que $filter
aquí se aplica "después" de TODO resultados de $lookup
se devuelven a la nueva matriz del documento principal.
Entonces, en términos de rendimiento, en realidad es más efectivo hacerlo de la primera manera, además de ser portátil a posibles conjuntos de resultados grandes "antes del filtrado" que, de lo contrario, romperían el límite de BSON de 16 MB.
Como nota al margen para aquellos que estén interesados, en futuras versiones de MongoDB (presumiblemente 3.6 y posteriores) puede usar $replaceRoot
en lugar de un $addFields
con el uso del nuevo $mergeObjects
operador de tubería La ventaja de esto es que como "bloque", podemos declarar el "filtered"
contenido como una variable a través de $let
, lo que significa que no necesita escribir el mismo $filter
"dos veces":
db.emailGroup.aggregate([
{ "$lookup": {
"from": "link",
"localField": "_id",
"foreignField": "emailGroupId",
"as": "link"
}},
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$$ROOT",
{ "$let": {
"vars": {
"filtered": {
"$filter": {
"input": "$link",
"as": "l",
"cond": { "$eq": [ "$$l.originalLink", "" ] }
}
}
},
"in": {
"link": "$$filtered",
"linkCount": {
"$sum": {
"$map": {
"input": "$$filtered.linkHistory",
"as": "lh",
"in": { "$size": { "$ifNull": [ "$$lh", [] ] } }
}
}
}
}
}}
]
}
}}
])
No obstante, la mejor manera de hacer tal "filtrado" $lookup
operaciones "todavía" en este momento usando el $unwind
entonces $match
patrón, hasta el momento en que pueda proporcionar argumentos de consulta a $lookup
directamente.