我在MongoDB中有这个查询:
db.emailGroup.aggregate([
{
"$lookup":
{
"from": "link",
"localField": "_id",
"foreignField": "emailGroupId",
"as": "link"
},
},
{
"$unwind": "$link"
},
{
"$match": {
'link.originalLink': ""
}
},
{
"$group" : {
_id: '$_id',
link: {
$push: '$link'
}
}
},
{
"$project": {
"size": {
"$sum": {
"$map": {
"input": "$link",
"as": "l",
"in": {
"$size": {
"$ifNull": [
"$$l.linkHistory", []
]
}
}
}
}
}
}
}
])
EmailGroup有partId字段。我使用$ lookup来“加入”其他集合来汇总她的字段。我需要group by partId字段并为partId组求和自定义字段“size”。这可能吗?额外问题:如何将emailGroup字段添加到查询结果中?
示例文件:
emailGroup:
{
"_id" : ObjectId("594a6c47f51e075db713ccb6"),
"partId" : "f56c7c71eb14a20e6129a667872f9c4f",
}
链接:
{
"_id" : ObjectId("594b96d6f51e075db67c44c9"),
"originalLink" : "",
"emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
"linkHistory" : [
{
"_id" : ObjectId("594b96f5f51e075db713ccdf"),
},
{
"_id" : ObjectId("594b971bf51e075db67c44ca"),
}
]
}
答案 0 :(得分:4)
比最初回答的要好一点,就是实际使用MongoDB 3.6中较新的$lookup
形式。这实际上可以在“子管道”表达式中进行“计数”,而不是返回“数组”以进行后续过滤和计数,甚至使用$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如何处理将$lookup
的结果“加入”父文档数组的结果可能超出BSON限制的可能性。
您可以交替编写如下操作:
所有细节
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", [] ] } }
}
}
}
}}
])
按分区ID分组
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
此处已应用于“{strong>所有结果$lookup
返回到父文档的新数组。
因此,在性能方面,以第一种方式实现它以及在“过滤之前”可移植到可能的大型结果集实际上更有效,否则会破坏16MB BSON限制。
注意:如果您没有
$addFields
可用(与MongoDB 3.4一起添加),请使用$project
并指定您想要返回的“所有”字段。
作为感兴趣的人的旁注,在MongoDB的未来版本(可能是3.6及更高版本)中,您可以使用$replaceRoot
而不是$addFields
来使用新版{{1}管道运营商。这样做的好处是作为“块”,我们可以通过$let
将$mergeObjects
内容声明为变量,这意味着您不需要编写相同的$filter
“两次”:
"filtered"
尽管如此,使用$lookup
然后$unwind
模式进行此类“过滤”$match
操作的最佳方法仍然是“仍然”,直到您可以提供查询参数为止直接到$lookup
。
注意:通过“直接”在这里我并不意味着
$lookup
的“非相关”形式,它也应该出现在MongoDB 3.6版本中,因为这确实会发出另一个父集合中每个文档的“管道”执行。因此,该功能仍然无法取代目前仅检索匹配项目的“最有效”方式。