Annotation til dem, der leder efter - Udenlandsk Count
En smule bedre end det oprindeligt blev besvaret er faktisk at bruge den nyere form for $lookup
fra MongoDB 3.6. Dette kan faktisk "tælle" i "sub-pipeline" udtrykket i modsætning til at returnere en "array" til efterfølgende filtrering og optælling eller endda bruge $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" }
}}
])
Ikke hvad det oprindelige spørgsmål bad om, men en del af nedenstående svar i den nu mest optimale form, som selvfølgelig resultatet af $lookup
reduceres kun til "matchede antal". i stedet for "alle matchede dokumenter".
Original
Den korrekte måde at gøre dette på ville være at tilføje "linkCount"
til $group
fase samt en $first
på eventuelle yderligere felter i det overordnede dokument for at få "ental"-formen, som var tilstanden "før" $unwind
blev behandlet på det array, der var resultatet af $lookup
:
Alle 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", [] ]
}
}
}
}}
])
Producerer:
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
"link" : [
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf")
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca")
}
]
}
],
"linkCount" : 2
}
Gruppér 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", [] ]
}
}
}
}}
])
Producerer
{
"_id" : "f56c7c71eb14a20e6129a667872f9c4f",
"linkCount" : 2
}
Grunden til at du gør det på denne måde med en $unwind
og derefter en $match
er på grund af, hvordan MongoDB faktisk håndterer pipelinen, når den udstedes i den rækkefølge. Dette er, hvad der sker med $lookup
som det er demonstreret, er "explain"
output fra operationen:
{
"$lookup" : {
"from" : "link",
"as" : "link",
"localField" : "_id",
"foreignField" : "emailGroupId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"originalLink" : {
"$eq" : ""
}
}
}
},
{
"$group" : {
Jeg forlader delen med $group
i det output for at demonstrere, at de to andre pipeline-faser "forsvinder". Dette skyldes, at de er blevet "rullet op" i $lookup
rørledningstrin som vist. Det er faktisk sådan, MongoDB håndterer muligheden for, at BSON-grænsen kan overskrides af resultatet af "joining"-resultater af $lookup
ind i en matrix af det overordnede dokument.
Du kan skiftevis skrive handlingen sådan her:
Alle 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", [] ] } }
}
}
}
}}
])
Grupper efter 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" }
}}
])
Som har samme output, men "adskiller sig" fra den første forespørgsel ved, at $filter
her anvendes "efter" ALLE resultater af $lookup
returneres til det nye array i det overordnede dokument.
Så med hensyn til ydeevne er det faktisk mere effektivt at gøre det på den første måde samt at være bærbart til mulige store resultatsæt "før filtrering", som ellers ville bryde 16 MB BSON-grænsen.
Som en sidebemærkning til dem, der er interesserede, kan du i fremtidige udgivelser af MongoDB (formentlig 3.6 og nyere) bruge $replaceRoot
i stedet for en $addFields
med brug af de nye $mergeObjects
rørledningsoperatør. Fordelen ved dette er som en "blok", vi kan erklære $let
, hvilket betyder, at du ikke behøver at skrive det samme $filter
"to gange":
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", [] ] } }
}
}
}
}
}}
]
}
}}
])
Ikke desto mindre er den bedste måde at gøre sådan en "filtreret" $lookup
driften er "stadig" på nuværende tidspunkt ved hjælp af $unwind
derefter $match
mønster, indtil du kan give forespørgselsargumenter til $ opslag
direkte.