Анотация за тези, които търсят - Чуждестранна графа
Малко по-добре от първоначалния отговор е действително да се използва по-новата форма на $lookup
от MongoDB 3.6. Това всъщност може да извърши „броенето“ в рамките на израза „подтръбопровод“ за разлика от връщането на „масив“ за последващо филтриране и преброяване или дори използване на $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" }
}}
])
Не това, което първоначалният въпрос искаше, а част от отговора по-долу в сега най-оптималната форма, като разбира се резултат от $lookup
се редуцира до само броя на съвпаденията вместо „всички съвпадащи документи“.
Оригинал
Правилният начин да направите това е да добавите "linkCount"
към $group
етап, както и $first
във всички допълнителни полета на основния документ, за да получите формата "единично число", както беше състоянието "преди" $unwind
беше обработен в масива, който беше резултат от $lookupкод>
:
Всички подробности
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", [] ]
}
}
}
}}
])
Произвежда:
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
"link" : [
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf")
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca")
}
]
}
],
"linkCount" : 2
}
Групиране по 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", [] ]
}
}
}
}}
])
Произвежда
{
"_id" : "f56c7c71eb14a20e6129a667872f9c4f",
"linkCount" : 2
}
Причината да го направите по този начин с $unwindкод>
и след това $match
се дължи на начина, по който MongoDB всъщност обработва тръбопровода, когато е издаден в този ред. Ето какво се случва с $lookup
както е демонстрирано с "explain"
изход от операцията:
{
"$lookup" : {
"from" : "link",
"as" : "link",
"localField" : "_id",
"foreignField" : "emailGroupId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"originalLink" : {
"$eq" : ""
}
}
}
},
{
"$group" : {
Напускам частта с $group
в този изход, за да демонстрира, че другите два етапа на тръбопровода „изчезват“. Това е така, защото те са били „събрани“ в $lookup
етап на тръбопровода, както е показано. Това всъщност е начинът, по който MongoDB се справя с възможността лимитът на BSON да бъде надвишен от резултата от „присъединяването“ на резултатите от $lookup
в масив от основния документ.
Можете алтернативно да напишете операцията по следния начин:
Всички подробности
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", [] ] } }
}
}
}
}}
])
Групиране по 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" }
}}
])
Което има същия изход, но се „различава“ от първата заявка по това, че $filter
тук се прилага "след" ВСИЧКИ резултати от $lookup
се връщат в новия масив на родителския документ.
Така че от гледна точка на производителността всъщност е по-ефективно да го направите по първия начин, както и да бъдете преносими към възможни големи набори от резултати „преди филтриране“, което иначе би нарушило ограничението от 16MB BSON.
Като странична бележка за тези, които се интересуват, в бъдещи издания на MongoDB (вероятно 3.6 и по-нови) можете да използвате $replaceRoot
вместо $addFields
с използването на новия $mergeObjects
оператор на тръбопровод. Предимството на това е, че като "блок", можем да декларираме "филтриран"
съдържание като променлива чрез $let
, което означава, че не е необходимо да пишете същия $filter
"два пъти":
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", [] ] } }
}
}
}
}
}}
]
}
}}
])
Въпреки това най-добрият начин да направите такъв "филтриран" $lookupкод>
операциите са "все още" в този момент с помощта на $unwindкод>
след това $match
шаблон, докато можете да предоставите аргументи на заявката на $ търсене
директно.