Annotation pour ceux qui recherchent - Compte étranger
Un peu mieux que la réponse initiale consiste à utiliser la nouvelle forme de $lookup
de MongoDB 3.6. Cela peut en fait faire le "comptage" dans l'expression "sous-pipeline" au lieu de renvoyer un "tableau" pour un filtrage et un comptage ultérieurs ou même d'utiliser $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" }
}}
])
Pas ce que la question initiale demandait, mais une partie de la réponse ci-dessous sous la forme désormais la plus optimale, comme bien sûr le résultat de $lookup
est réduit au "nombre de correspondances" uniquement au lieu de "tous les documents correspondants".
Original
La bonne façon de procéder serait d'ajouter le "linkCount"
au $group
stage ainsi qu'un $first
sur tous les champs supplémentaires du document parent afin d'obtenir la forme "singulière" comme l'était l'état "avant" le $unwind
a été traité sur le tableau qui était le résultat de $lookup
:
Tous les détails
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", [] ]
}
}
}
}}
])
Produit :
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
"link" : [
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf")
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca")
}
]
}
],
"linkCount" : 2
}
Regrouper par partId
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", [] ]
}
}
}
}}
])
Produit
{
"_id" : "f56c7c71eb14a20e6129a667872f9c4f",
"linkCount" : 2
}
La raison pour laquelle vous le faites de cette façon avec un $unwind
puis un $match
est dû à la façon dont MongoDB gère réellement le pipeline lorsqu'il est émis dans cet ordre. C'est ce qui arrive au $lookup
comme le montre le "explain"
résultat de l'opération :
{
"$lookup" : {
"from" : "link",
"as" : "link",
"localField" : "_id",
"foreignField" : "emailGroupId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"originalLink" : {
"$eq" : ""
}
}
}
},
{
"$group" : {
Je quitte la partie avec $group
dans cette sortie pour démontrer que les deux autres étapes du pipeline "disparaissent". C'est parce qu'ils ont été "regroupés" dans le $lookup
étape de pipeline comme indiqué. C'est en fait ainsi que MongoDB gère la possibilité que la limite de BSON puisse être dépassée par le résultat de "rejoindre" les résultats de $lookup
dans un tableau du document parent.
Vous pouvez alternativement écrire l'opération comme ceci :
Tous les détails
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", [] ] } }
}
}
}
}}
])
Regrouper par 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" }
}}
])
Qui a le même résultat mais "diffère" de la première requête en ce que le $filter
ici est appliqué "après" TOUT résultats de la $lookup
sont renvoyés dans le nouveau tableau du document parent.
Donc, en termes de performances, il est en fait plus efficace de le faire de la première manière tout en étant portable vers d'éventuels ensembles de résultats volumineux "avant le filtrage", ce qui autrement dépasserait la limite BSON de 16 Mo.
En remarque pour ceux qui sont intéressés, dans les futures versions de MongoDB (vraisemblablement 3.6 et plus), vous pouvez utiliser $replaceRoot
au lieu d'un $addFields
avec l'utilisation du nouveau $mergeObjects
exploitant de pipeline. L'avantage de ceci est qu'en "bloc", on peut déclarer le "filtered"
contenu en tant que variable via $let
, ce qui signifie que vous n'avez pas besoin d'écrire le même $filter
"deux fois":
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", [] ] } }
}
}
}
}
}}
]
}
}}
])
Néanmoins, la meilleure façon de faire un tel "filtré" $lookup
opérations est "toujours" en ce moment en utilisant le $unwind
puis $match
modèle, jusqu'à ce que vous puissiez fournir des arguments de requête à $lookup
directement.