【问题标题】:Get latest message in conversation获取对话中的最新消息
【发布时间】:2017-05-13 06:55:33
【问题描述】:

假设我有两个 Mongoose 集合 - conversationmessage - 我想显示特定用户正在进行的对话列表,按最新消息排序,并在下方显示该消息的预览对话名称

在获得用户的对话后,如何仅从每个对话中选择最新消息,并将这些消息附加到相应的对话中? (鉴于架构看起来像这样):

var ConversationSchema = new Schema({
    name: String,
    participants: {
        type: [{
            type: Schema.Types.ObjectId,
            ref: 'User'
        }]
    }
});

var MessageSchema = new Schema({
    conversation: {type: Schema.Types.ObjectId, ref: 'Conversation', required: true},
    text: {type: String, required: true},
    user: {type: Schema.Types.ObjectId, ref: 'User', required: true}
});

我收到消息说我可能应该使用 Mongo 的“聚合”框架,但我以前从未使用过它,所以我需要一些帮助。谢谢!

【问题讨论】:

    标签: node.js mongodb mongoose mongodb-query aggregation-framework


    【解决方案1】:

    聚合框架确实是您完成这项任务的好伙伴。通过聚合,它通过使用过滤器、分组器、分类器、转换和其他运算符的多级管道将集合提取到基本信息来工作。提炼出的结果集比其他技术更有效。

    对于上述用例,聚合由一系列特殊运算符组成,这些运算符应用于称为管道的集合:

    [
        { "$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 阶段将这些重新调整的文档传递到下一个阶段。

    既然您已经获得了用户的conversationsmessages,那么如何从每个对话中选择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 步骤。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-21
      • 1970-01-01
      • 2015-11-06
      • 1970-01-01
      • 2020-12-14
      • 2016-03-24
      • 2016-01-21
      • 1970-01-01
      相关资源
      最近更新 更多