Anteckning för de som letar efter - Utländsk räkning
Lite bättre än vad som ursprungligen besvarades är att faktiskt använda den nyare formen av $lookup
från MongoDB 3.6. Detta kan faktiskt göra "räkningen" inom "sub-pipeline"-uttrycket i motsats till att returnera en "array" för efterföljande filtrering och räkning eller till och med använda $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" }
}}
])
Inte vad den ursprungliga frågan efterfrågade utan en del av svaret nedan i den nu mest optimala formen, som naturligtvis resultatet av $lookup
reduceras till endast "matchat antal". istället för "alla matchade dokument".
Original
Det korrekta sättet att göra detta är att lägga till "linkCount"
till $group
skede samt en $first
på eventuella ytterligare fält i det överordnade dokumentet för att få den "singular" formen som var tillståndet "före" $unwind
bearbetades på arrayen som var resultatet av $lookup
:
Alla detaljer
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", [] ]
}
}
}
}}
])
Producerar:
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
"link" : [
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf")
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca")
}
]
}
],
"linkCount" : 2
}
Gruppera efter 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", [] ]
}
}
}
}}
])
Producerar
{
"_id" : "f56c7c71eb14a20e6129a667872f9c4f",
"linkCount" : 2
}
Anledningen till att du gör det på det här sättet med en $unwind
och sedan en $match
beror på hur MongoDB faktiskt hanterar pipelinen när den utfärdas i den ordningen. Detta är vad som händer med $lookup
som visas är "explain"
utdata från operationen:
{
"$lookup" : {
"from" : "link",
"as" : "link",
"localField" : "_id",
"foreignField" : "emailGroupId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"originalLink" : {
"$eq" : ""
}
}
}
},
{
"$group" : {
Jag lämnar delen med $group
i den utgången för att visa att de andra två pipeline-stegen "försvinner". Detta beror på att de har "rullats upp" till $lookup
rörledningssteg som visas. Det är faktiskt så MongoDB hanterar möjligheten att BSON-gränsen kan överskridas av resultatet av "joining"-resultat av $lookup
i en array av det överordnade dokumentet.
Du kan alternativt skriva operationen så här:
Alla detaljer
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", [] ] } }
}
}
}
}}
])
Gruppera efter partiId
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" }
}}
])
Som har samma utdata men "skiljer sig" från den första frågan genom att $filter
här tillämpas "efter" ALLA resultat av $lookup
returneras till den nya arrayen i det överordnade dokumentet.
Så i prestandatermer är det faktiskt mer effektivt att göra det på första sättet samt att vara portabel till möjliga stora resultatuppsättningar "före filtrering" som annars skulle bryta 16MB BSON-gränsen.
Som en sidoanteckning för de som är intresserade, i framtida versioner av MongoDB (förmodligen 3.6 och uppåt) kan du använda $replaceRoot
istället för en $addFields
med användning av nya $mergeObjects
rörledningsoperatör. Fördelen med detta är som ett "block", vi kan deklarera $let
, vilket innebär att du inte behöver skriva samma $filter
"två gånger":
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", [] ] } }
}
}
}
}
}}
]
}
}}
])
Icke desto mindre är det bästa sättet att göra sådana "filtrerade" $lookup
driften är "fortfarande" för närvarande med $unwind
sedan $match
mönster, tills du kan tillhandahålla frågeargument till $ uppslag
direkt.