Adnotacja dla tych, którzy szukają – liczba zagraniczna
Nieco lepiej niż pierwotnie udzielono odpowiedzi, jest użycie nowszej formy $wyszukaj
z MongoDB 3.6. Może to faktycznie wykonać „liczenie” w wyrażeniu „podpotoku”, w przeciwieństwie do zwracania „tablicy” do późniejszego filtrowania i liczenia, a nawet przy użyciu $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" }
}}
])
Nie to, o co prosiło pierwotne pytanie, ale część poniższej odpowiedzi w obecnie najbardziej optymalnej formie, co jest oczywiście wynikiem $lookup
jest zredukowana do „dopasowanej liczby” tylko zamiast "wszystkie pasujące dokumenty".
Oryginał
Właściwym sposobem na zrobienie tego byłoby dodanie "linkCount"
do $group
etap oraz $first
na dowolnych dodatkowych polach dokumentu nadrzędnego w celu uzyskania formy „w liczbie pojedynczej”, takiej jak stan „przed” $unwind
zostało przetworzone na tablicy, która była wynikiem $lookup
:
Wszystkie szczegóły
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", [] ]
}
}
}
}}
])
Produkuje:
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
"link" : [
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf")
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca")
}
]
}
],
"linkCount" : 2
}
Grupuj według 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", [] ]
}
}
}
}}
])
Produkuje
{
"_id" : "f56c7c71eb14a20e6129a667872f9c4f",
"linkCount" : 2
}
Powód, dla którego robisz to w ten sposób z $unwind
a następnie $match
Wynika to z tego, jak MongoDB faktycznie obsługuje potok, gdy jest wydawany w tej kolejności. Tak dzieje się z $lookup
jak pokazano w "wyjaśnij"
wyjście z operacji:
{
"$lookup" : {
"from" : "link",
"as" : "link",
"localField" : "_id",
"foreignField" : "emailGroupId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"originalLink" : {
"$eq" : ""
}
}
}
},
{
"$group" : {
Zostawiam część z $group
w tym wyniku, aby wykazać, że pozostałe dwa etapy rurociągu „znikają”. Dzieje się tak, ponieważ zostały one „zwinięte” w $oglądaj
etap rurociągu, jak pokazano. W ten sposób MongoDB radzi sobie z możliwością przekroczenia limitu BSON w wyniku „łączenia” wyników $lookup
do tablicy dokumentu nadrzędnego.
Możesz naprzemiennie napisać operację w ten sposób:
Wszystkie szczegóły
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", [] ] } }
}
}
}
}}
])
Grupuj według 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" }
}}
])
Który ma to samo wyjście, ale „różni się” od pierwszego zapytania tym, że $filtr
tutaj jest stosowane „po” WSZYSTKO wyniki $lookup
są zwracane do nowej tablicy dokumentu nadrzędnego.
Tak więc pod względem wydajności, bardziej efektywne jest zrobienie tego w pierwszy sposób, jak również możliwość przenoszenia do możliwych dużych zestawów wyników „przed filtrowaniem”, które w przeciwnym razie przekroczyłoby limit 16 MB BSON.
Na marginesie dla zainteresowanych, w przyszłych wydaniach MongoDB (przypuszczalnie 3.6 i nowszych) możesz użyć $replaceRoot
zamiast $addFields
z użyciem nowego $mergeObjects
operator rurociągu. Zaletą tego jest "blok", możemy zadeklarować "filtrowane"
treść jako zmienną za pośrednictwem $let
, co oznacza, że nie musisz pisać tego samego $filter
"dwa razy":
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", [] ] } }
}
}
}
}
}}
]
}
}}
])
Niemniej jednak najlepszy sposób na zrobienie takich „przefiltrowanych” $lookup
operacje są w tej chwili „wciąż” przy użyciu $unwind
następnie $match
wzorca, do czasu, gdy będziesz mógł dostarczyć argumenty zapytania do $ wyszukiwanie
bezpośrednio.