【问题标题】:Query from two collections in MongoDB从 MongoDB 中的两个集合中查询
【发布时间】:2020-02-06 00:24:18
【问题描述】:

我正在尝试在 MongoDB 中找到自己的方法。这是我第一次使用这个数据库,来自 MySQL。但对于我正在制作的聊天应用程序,建议使用 MongoDB 更合适。

我有两个收藏:

对话,我在其中存储成员用户 ID(存储在 MySQL 数据库中)和加入日期。

{
    "_id" : ObjectId("5e35f2c840713a43aeeeb3d9"),
    "members" : [ 
        {
            "uID" : "1",
            "j" : 1580580922
        }, 
        {
            "uID" : "4",
            "j" : 1580580922
        }, 
        {
            "uID" : "5",
            "j" : 1580580922
        }
    ]
}

消息,我在其中存储发件人(用户 ID)、消息、时间戳、对话 ID(来自上面的集合)、读取和传递状态

{
    "_id" : ObjectId("5e35ee5f40713a43aeeeb1c5"),
    "c_ID" : ObjectId("5e35f2c840713a43aeeeb3d9"),
    "fromID" : "1",
    "msg" : "What's up?",
    "t" : 1580591922,
    "d" : {
        "4" : 1580592039
    },
    "r" : {
        "4" : 1580592339
    }
}

我现在要做的是查询特定用户的对话,比如 userID 1,以及该对话中发送的最后一条消息。

我想出了以下几点:

db.getCollection('conversations').aggregate(
[{
    $match: {
        "members.uID": "1"
    }
},
{
    $lookup: {
        as: 'lastMessage',
        foreignField: 'c_ID',
        from: 'messages',
        localField: '_id',
    }
},
])

但这里的问题是它列出了所有消息,而不仅仅是最后一条。所以我想将其限制为 1,或者如果有其他方法..请告诉我。

感谢任何帮助!

【问题讨论】:

  • 如果您不依赖于这些特定模式,我建议将 messages 作为数组存储在 conversation 中。这应该会大大简化您想要执行的任何聚合查询,以在对话中查找特定消息(无论是第一条消息、最新消息、来自某个用户的所有消息等)。这也可能更适合您的聊天应用程序如何使用对话和消息。从本质上讲,这消除了在对话中查找消息所需的“加入”。
  • 我确实考虑过这一点,但是每次添加消息时我都需要执行更新。这不是很高效,是吗?
  • 插入可能比更新快,但如果您通过 _id 更新对话并且只添加新消息,则可能不会快很多。我认为将消息包含在对话中的好处超过写入性能稍慢的好处:您的数据结构更自然,在概念上更容易使用,并且读取性能得到改善,因为现在您不再需要两次调用应用程序加载对话的数据库(1 获取对话,然后 1 获取对话中的所有消息)。
  • 下面的解决方案会不会不好?另外,如果消息很多,持有所有ID可能会达到16mb的限制,不是吗?
  • 我看到的下面的解决方案没有任何问题(尽管我已经有一段时间没有做很多聚合了)。您可能会遇到包含大量消息的文档大小限制,所以如果这是一个现实的问题,我会考虑保留一个名为 oldMessages 的单独集合:此集合中的每个对象都包含一个消息数组,并且可以选择oldMessages 中另一个对象的 _id 用于更旧的消息。您的 conversation 条目可能有也可能没有“oldMessages”字段,如果存在的话,它是已经存在的消息之后的下一个最旧消息集的 _id。

标签: mongodb mongodb-query


【解决方案1】:

我想我们可以理解来自时间戳字段的最后一条消息。

$match$lookup阶段之后,我们需要$unwind消息,然后$sort通过时间戳。

现在消息数组中的第一条消息是lastMessage,所以当我们group时,我们将第一条消息推送为lastMessage,最后$replaceRoot形成我们的结果。

如果是这样,您可以使用以下聚合:

db.conversations.aggregate([
  {
    $match: {
      "members.uID": "1"
    }
  },
  {
    $lookup: {
      foreignField: "c_ID",
      from: "messages",
      localField: "_id",
      as: "messages"
    }
  },
  {
    "$unwind": "$messages"
  },
  {
    "$sort": {
      "messages.t": -1
    }
  },
  {
    "$group": {
      "_id": "$_id",
      "lastMessage": {
        "$first": "$messages"
      },
      "allFields": {
        "$first": "$$ROOT"
      }
    }
  },
  {
    "$replaceRoot": {
      "newRoot": {
        "$mergeObjects": [
          "$allFields",
          {
            "lastMessage": "$lastMessage"
          }
        ]
      }
    }
  },
  {
    $project: {
      messages: 0
    }
  }
])

如果messages数组已经排序,则可以简化解决方案,但这是一个通用的解决方案。

Playground

【讨论】:

  • 似乎做我想做的事!我唯一剩下的就是,我现在有 messages 和 lastMessage,包含相同的数据。我可以“隐藏”或删除消息对象吗?还是需要执行查询?
  • @PennyWise 您可以使用project 阶段轻松删除不需要的字段,我更新了答案。请不要忘记标记此答案并投票。
  • 我似乎错过了更新答案中的项目阶段。可以再添加一次吗?
猜你喜欢
  • 2016-09-27
  • 1970-01-01
  • 2020-09-19
  • 2012-05-11
  • 1970-01-01
  • 1970-01-01
  • 2020-03-24
  • 2016-12-13
  • 1970-01-01
相关资源
最近更新 更多