聚合框架确实是您完成这项任务的好伙伴。通过聚合,它通过使用过滤器、分组器、分类器、转换和其他运算符的多级管道将集合提取到基本信息来工作。提炼出的结果集比其他技术更有效。
对于上述用例,聚合由一系列特殊运算符组成,这些运算符应用于称为管道的集合:
[
{ "$match": { /* options */ } },
{ "$lookup": { /* options */ } },
{ "$group": { /* options */ } }
]
在执行管道时,MongoDB 将操作符通过管道传递给彼此。这里的“管道”取的是 Linux 的意思:一个算子的输出变成后面一个算子的输入。每个运算符的结果是一个新的文档集合。所以Mongo执行前面的管道如下:
collection | $match | $lookup | $group => result
现在,将上述内容应用到您的任务中,您需要一个 $match 管道步骤作为聚合的第一阶段,因为它允许您过滤文档流,仅匹配未修改的文档传入下一个管道阶段。因此,如果您在 Conversation 模型上运行聚合,只需
$match 查询对象,您将获得特定用户所在的文档:
Conversation.aggregate([
{ "$match": { "participants": userId } }
]).exec(function(err, result){
if (err) throw err;
console.log(result);
})
您可以通过管道传递另一个运算符,在这种情况下,您需要 $lookup 运算符,该运算符对同一数据库中的messages 集合执行左外连接,以过滤来自“已连接”的文档" 收集处理:
Conversation.aggregate([
{ "$match": { "participants": userId } },
{
"$lookup": {
"from": "messages",
"localfield": "_id",
"foreignField": "conversation",
"as": "messages"
}
}
]).exec(function(err, result){
if (err) throw err;
console.log(result);
})
这将输出到控制台来自conversation 文档的字段以及一个名为“messages”的新数组字段,其元素是来自“joined”messages 集合的匹配文档。 $lookup 阶段将这些重新调整的文档传递到下一个阶段。
既然您已经获得了用户的conversations 和messages,那么如何从每个对话中选择ONLY 的最新消息?
这可以通过 $group 管道运算符来实现,但在对上一个管道中的文档应用 $group 运算符之前,
您需要展平 messages 数组以获取最新文档,$unwind 运算符将为您解构数组元素。
当 $unwind 运算符应用于数组字段时,它将为列表中 $unwind 所在的每个元素生成一条新记录应用:
Conversation.aggregate([
{ "$match": { "participants": userId } },
{
"$lookup": {
"from": "messages",
"localfield": "_id",
"foreignField": "conversation",
"as": "messages"
}
},
{ "$unwind": "$messages" }
]).exec(function(err, result){
if (err) throw err;
console.log(result);
})
假设您的MessageSchema 有一个时间戳字段(例如createdAt),它表示发送消息的日期时间,您可以按该字段以降序对文档重新排序,以便处理到下一个管道。 $sort 运算符非常适合:
Conversation.aggregate([
{ "$match": { "participants": userId } },
{
"$lookup": {
"from": "messages",
"localfield": "_id",
"foreignField": "conversation",
"as": "messages"
}
},
{ "$unwind": "$messages" },
{ "$sort": { "messages.createdAt": -1 } }
]).exec(function(err, result){
if (err) throw err;
console.log(result);
})
拥有非规范化文档后,您可以对数据进行分组以进行处理。组管道运算符类似于 SQL 的GROUP BY 子句。在 SQL 中,您不能使用GROUP BY,除非您使用任何聚合函数(称为accumulators)。同样,您也必须在 MongoDB 中使用聚合函数。 MongoDB 仅使用 _id 字段识别分组表达式,在这种情况下,通过 _id 字段对文档进行分组:
Conversation.aggregate([
{ "$match": { "participants": userId } },
{
"$lookup": {
"from": "messages",
"localfield": "_id",
"foreignField": "conversation",
"as": "messages"
}
},
{ "$unwind": "$messages" },
{ "$sort": { "messages.createdAt": -1 } }
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"participants": { "$first": "$participants" },
"latestMessage": { "$first": "$message" }
}
}
]).exec(function(err, result){
if (err) throw err;
console.log(result);
})
在上面,我们对 $first 组累加器运算符感兴趣,因为它从每个组的第一个文档返回一个值。因为前面的管道运算符按降序对文档进行排序,所以 $first 会给你LATEST 消息。
因此,运行上述最后一个聚合操作将为您提供所需的结果。这假设 MessageSchema 有一个时间戳,这对于确定最新消息是必不可少的,否则它只能工作到 $unwind 步骤。