而不是.populate()(这是“客户端”端操作),您希望数据在服务器上“加入”,其中.aggregate() 是“服务器”端操作。这就是 $lookup 运算符的用途:
在执行$lookup 之前,首先使用$map 和$concatArrays 可能是最佳选择
Song.aggregate([
{ "$project": {
"author": "$author",
"data": {
"$concatArrays": [
{ "$map": {
"input": "$views",
"as": "el",
"in": { "type": "views", "_id": "$$el" }
}},
{ "$map": {
"input": "$likes",
"as": "el",
"in": { "type": "likes", "_id": "$$el" }
}}
]
}
}},
{ "$unwind": "$data" },
{ "$lookup": {
"from": "users",
"localField": "data._id",
"foreignField": "_id",
"as": "data._id"
}},
{ "$unwind": "$data._id" },
{ "$group": {
"_id": {
"author": "$author",
"continent": "$data._id.continent"
},
"views": {
"$sum": { "$cond": [ { "$eq": [ "$data.type", "views" ] }, 1, 0 ] }
},
"likes": {
"$sum": { "$cond": [ { "$eq": [ "$data.type", "likes" ] }, 1, 0 ] }
}
}}
], function(err, results) {
})
您在开始时执行“数组连接”,因为在某些时候您希望“喜欢”和“视图”在一个数组中,因为如果我们尝试通过以后的 $unwind 操作单独处理它们(并且您需要为了“计算”“continent”的值)然后你最终得到一个“笛卡尔积”,因为一个数组的内容将乘以另一个数组中的内容。
因此,我们用“类型”标记“加入”,因为它们不再位于单独的字段中,并且我们仍然需要区分“喜欢”和“浏览量”以进行计数。
$lookup 操作在最现代的版本中能够处理“平面”数组,但不能处理从第一阶段构造的“文档数组”。只需$unwind 先处理即可。
一旦$lookup 完成,这种形式的结果将是"data._id" 路径中包含的每个结果的单个元素数组。为了继续处理我们再次$unwind。
最后是$group,其中“复合键”用于从连接数据中获得的“作者”和“大陆”值。为了计数,“喜欢”和“浏览”中的每一个都应用于 $cond 表达式,这是一个三元 (if/then/else) 运算符。给定第一个参数中的条件,条件为true,则返回第二个参数值,或者当false 第三个参数时。
这些表达式的结果被传递给$sum 进行累加,因此当条件匹配时,返回正数并为分组键累加。
所有聚合都是“繁重”的操作,而执行“joins”实际上被认为更加“繁重”。
在很多情况下,您的应用程序在运行时执行此类查询并没有什么问题。这真的归结为这是否以有效的速度运行在您的数据上。如果数据足够大以至于此类操作花费过多时间,那么您应该通过在单独的记录中累积此类摘要数据来“预先汇总”。在这种情况下,增加每个大陆的每个作者的“喜欢”或“浏览”计数等。