Anotace pro ty, kteří hledají - Cizí hrabě
O něco lepší, než bylo původně zodpovězeno, je skutečně použít novější formu $lookup
z MongoDB 3.6. To může ve skutečnosti provést "počítání" ve výrazu "sub-pipeline" na rozdíl od vracení "pole" pro následné filtrování a počítání nebo dokonce pomocí $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" }
}}
])
Ne to, co požadovala původní otázka, ale část níže uvedené odpovědi v nyní nejoptimálnější podobě, jako samozřejmě výsledek $lookup
pouze se sníží na „počet shodných položek“. místo "všechny odpovídající dokumenty".
Původní
Správný způsob, jak to udělat, je přidat "linkCount"
do $group
fázi a také $first
v jakýchkoli dalších polích nadřazeného dokumentu, abyste získali „singulární“ formu, jako byl stav „před“ $unwind
byl zpracován na poli, které bylo výsledkem $lookup
:
Všechny podrobnosti
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", [] ]
}
}
}
}}
])
Vyrábí:
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
"link" : [
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf")
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca")
}
]
}
],
"linkCount" : 2
}
Seskupit podle 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
}
Důvod, proč to děláte tímto způsobem s $unwind
a poté $match
je to kvůli tomu, jak MongoDB ve skutečnosti zpracovává potrubí, když je vydáváno v tomto pořadí. Toto se stane s $lookup
jak je demonstrováno "vysvětlit"
výstup z operace:
{
"$lookup" : {
"from" : "link",
"as" : "link",
"localField" : "_id",
"foreignField" : "emailGroupId",
"unwinding" : {
"preserveNullAndEmptyArrays" : false
},
"matching" : {
"originalLink" : {
"$eq" : ""
}
}
}
},
{
"$group" : {
Opouštím část s $group
v tomto výstupu demonstrovat, že další dva stupně potrubí „zmizí“. Důvodem je, že byly „srolovány“ do $lookup
fáze potrubí, jak je znázorněno. Takto se MongoDB ve skutečnosti vypořádává s možností, že limit BSON může být překročen výsledkem „spojení“ výsledků $lookup
do pole nadřazeného dokumentu.
Operaci můžete střídavě napsat takto:
Všechny podrobnosti
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", [] ] } }
}
}
}
}}
])
Seskupit podle 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" }
}}
])
Který má stejný výstup, ale "liší se" od prvního dotazu tím, že $filter
zde se použije "po" VŠECHNY výsledky $lookup
jsou vráceny do nového pole nadřazeného dokumentu.
Z hlediska výkonu je tedy ve skutečnosti efektivnější to udělat prvním způsobem a zároveň být přenositelný na možné velké sady výsledků „před filtrováním“, které by jinak porušilo limit 16 MB BSON.
Jako vedlejší poznámku pro ty, kteří mají zájem, v budoucích vydáních MongoDB (pravděpodobně 3.6 a vyšší) můžete použít $replaceRoot
místo $addFields
s použitím nového $mergeObjects
provozovatel potrubí. Výhodou je, že jako "blok" můžeme prohlásit "filtrovaný"
obsah jako proměnná prostřednictvím $let
, což znamená, že nemusíte psát stejný $filter
"dvakrát":
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", [] ] } }
}
}
}
}
}}
]
}
}}
])
Nicméně nejlepší způsob, jak udělat takové "filtrované" $lookup
operace je v tuto chvíli „stále“ pomocí $unwind
pak $match
vzor, dokud nebudete moci poskytnout argumenty dotazu $ vyhledávání
přímo.