首页 > 解决方案 > MongoDB 允许多个加入和离开日期

问题描述

感谢 StackOverflow 上的社区,我目前有一个查询。

用户可以进行一对一的对话,也可以与多达 25 人进行群聊。我的数据库背后的想法是保留一个对话文档和一个消息文档,它们使用对话中的 _id 相互链接。这是我的对话文件:

{
    "_id" : ObjectId("5e35f2c840713a43aeeeb3d9"),
    "n" : "Example Group Chat",
    "members" : [ 
        {
            "uID" : "1",
            "j" : 1580580922,
            "i" : "1",
            "r" : "admin",
            "a" : 1
        }, 
        {
            "uID" : "4",
            "j" : 1580580922,
            "l" : 1581863346,
            "i" : "1",
            "r" : "member",
            "a" : 0
        }, 
        {
            "uID" : "5",
            "j" : 1580581922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "9",
            "j" : 1580593922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "3",
            "j" : 1580594920,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "8",
            "j" : 1580594999,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }
    ]
}

{
    "_id" : ObjectId("5e39d5d740713a43aeef5b26"),
    "members" : [ 
        {
            "uID" : "1",
            "j" : 1580580922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "2",
            "j" : 1580580922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }
    ]
}

您可以看到群聊(第一)和一对一聊天(第二)。群聊可以有名字(n),一对一聊天没有。每个对话都有一个成员数组,其中存储用户 ID (uID)、加入日期 (j)、离开日期 (l)、用户 ID 邀请字段 (i)、角色字段 (r) 和活动字段 (一个)。我可能不需要“活动”字段,因为我有一个加入/左时间戳,但仍然。稍后我可能会删除它,因此可能不会包含该字段。

接下来,我的messages文档如下:

{
    "_id" : ObjectId("5e4917bca59ce44ef2770086"),
    "c_ID" : ObjectId("5e35f2c840713a43aeeeb3d9"),
    "msg" : "Whats good?",
    "fromID" : "1",
    "__v" : 0,
    "t" : 1582369525,
    "d" : {
        "4" : 1582369525
    },
    "r" : {
        "4" : 1582369525
    }
}

这包含消息本身 (msg)、发送消息的用户 (fromID)、UNIX Epoch (t) 中的时间戳以及传递 (d) 和读取 (r) 的子集合,当然还有会话 ObjectID (c_ID)。

db.conversations.aggregate([
  {
    $match: {
      "members.uID": "4"
    }
  },
  {
    $addFields: {
      user: {
        $arrayElemAt: [
          {
            $filter: {
              input: "$members",
              as: "member",
              cond: {
                $eq: [
                  "$$member.uID",
                  "4"
                ]
              }
            }
          },
          0
        ]
      }
    }
  },
  {
    $lookup: {
      from: "messages",
      let: {
        user: "$user",
        conversatoinId: "$_id"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                {
                  $eq: [
                    "$c_ID",
                    "$$conversatoinId"
                  ]
                },
                {
                  $cond: {
                    if: {
                      $lt: [
                        {
                          $ifNull: [
                            "$$user.l",
                            0
                          ]
                        },
                        "$$user.j"
                      ]
                    },
                    then: true,
                    else: {
                      $lt: [
                        "$t",
                        "$$user.l"
                      ]
                    }
                  }
                },
                {
                  $gt: [
                    "$t",
                    "$$user.j"
                  ]
                }
              ]
            }
          },

        },
        {
          $sort: {
            "t": -1
          },

        }
      ],
      as: "messages"
    }
  },
  {
    $project: {
      lastMessage: {
        $arrayElemAt: [
          "$messages",
          0
        ]
      },
      n: 1,
      members: 1
    }
  },
  {
    $sort: {
      "lastMessage.t": 1
    }
  },
  {
    $project: {
      members: {
        $filter: {
          input: "$members",
          as: "member",
          cond: {
            $and: [
              {
                $ne: [
                  "$$member.uID",
                  "4"
                ]
              },
              {
                $or: [
                  {
                    $eq: [
                      "$$member.l",
                      undefined
                    ]
                  },
                  {
                    $lt: [
                      "$$member.l",
                      "$$member.j"
                    ]
                  }
                ]
              }
            ]
          }
        }
      },
      memberCount: {
        $size: {
          $filter: {
            input: "$members",
            as: "member",
            cond: {
              $and: [
                {
                  $ne: [
                    "$$member.uID",
                    "4"
                  ]
                },
                {
                  $or: [
                    {
                      $eq: [
                        "$$member.l",
                        undefined
                      ]
                    },
                    {
                      $lt: [
                        "$$member.l",
                        "$$member.j"
                      ]
                    }
                  ]
                }
              ]
            }
          }
        }
      },
      n: 1,
      lastMessage: 1
    }
  },
  {
    $match: {
      lastMessage: {
        $exists: true
      }
    }
  },
  {
    $limit: 10
  }
])

现在,问题:

想象有一个有 20 个成员的群聊。聊天的名称是“Funky Fridays”。用户 ID 4 加入了该群聊(我将j(加入)字段设置为1582475543(时间戳),参与了 twee 周然后离开(我将l(左)字段设置为1583685143(时间戳)。这一切都可以正常工作。但是,我怎么能1 周后(时间戳1584289943)再次将 userID 4 添加到同一个群聊中,并确保 userID 4 可以看到 lastMessage,如果它是在他第一次加入/离开之间,或者如果它是在他再次加入之后发布的?**

我希望能够将同一个用户多次添加到 members 数组,但使用不同的j(and l) 字段,然后查询 lastMessage 使其位于其中一个之间,这将允许我想要按照描述执行的操作以上。

所以基本上我希望我的conversations文件如下:

{
    "_id" : ObjectId("5e35f2c840713a43aeeeb3d9"),
    "n" : "Example Group Chat",
    "members" : [ 
        {
            "uID" : "1",
            "j" : 1580580922,
            "i" : "1",
            "r" : "admin",
            "a" : 1
        }, 
        {
            "uID" : "4",
            "j" : 1580580922,
            "l" : 1581863346,
            "i" : "1",
            "r" : "member",
            "a" : 0
        }, 
        {
            "uID" : "5",
            "j" : 1580581922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "9",
            "j" : 1580593922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "3",
            "j" : 1580594920,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "8",
            "j" : 1580594999,
            "i" : "1",
            "r" : "member",
            "a" : 1
        },
        {
            "uID" : "4",
            "j" : 1581982392,
            "i" : "1",
            "r" : "member",
            "a" : 0
        }, 
    ]
}

{
    "_id" : ObjectId("5e39d5d740713a43aeef5b26"),
    "members" : [ 
        {
            "uID" : "1",
            "j" : 1580580922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "2",
            "j" : 1580580922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }
    ]
}

所以userID 4 加入了,参与了聊天,离开了,后来又加入了。我希望他能够在他所有对话的概述中看到聊天。

关于如何实现这一目标的任何想法?

编辑:根据乔的建议,我想使用如下所示的内容。有什么意见吗?我想知道如何使用下面的结构仅显示最后一个离开时间戳(1581963346)之前的最新消息,而不是在这些消息之后发布的任何消息。或者,如果有多个加入/离开日期,我想显示最后一条匹配的消息。稍后,我将使用这些日期来显示允许用户查看的消息。所以只有那些他在群聊中的时候。

{
    "_id" : ObjectId("5e35f2c840713a43aeeeb3d9"),
    "n" : "Example Group Chat",
    "members" : [ 
        {
            "uID" : "1",
            "j" : 1580580922,
            "i" : "1",
            "r" : "admin",
            "a" : 1
        }, 
        {
            "uID" : "4",
            "events" : [
                { type: "join", date: 1581863346 },
                { type: "leave", date: 1581963346 }
            ],
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "5",
            "j" : 1580581922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "9",
            "j" : 1580593922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "3",
            "j" : 1580594920,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }
    ]
}

编辑 2:我的主要关注点是查询某人加入群组的日期和某人离开群组的日期。下面我举个例子。

对话 X

此时,我希望 A 能够在他的概览中看到对话 X,其中来自 B(上午 10:12)的消息作为最后一条消息。当我进入对话时,我希望将上午 10:00 到上午 10:15 之间的所有消息显示给 A 人。所以只有来自 C 人(上午 10:20)的最后一条消息不应该显示。

此时,我希望 A 人在他的概述中看到来自 C 人的最新消息。在对话本身中,我想显示上午 10:00 到 10:15 之间的消息,以及上午 10:32 到 10:38 之间的消息。

我只是想知道如何构建我的数据以及如何查询概述(基于我上面的查询)来实现这一点。稍后我会处理对话并为某个用户显示所有相关消息。

标签: mongodbperformancemongodb-query

解决方案


您可以尝试以下聚合。

在该$lookup阶段,拉入与对话 ID 匹配的消息,并跨多个加入和离开时间线,然后$sort降序$limit选择概述消息。

db.conversations.aggregate([
  {"$match":{"members.uID":"4"}},
  {"$lookup":{
    "from":"messages",
    "let":{
      "members":{
         "$filter":{
          "input":"$members",
         "cond":{"$eq":["$$this.uID","4"]}
         }
       },
      "conversation_id":"$_id"
    },
    "pipeline":[
      {"$match":{
        "$expr":{
          "$and":[
            {"$eq":["$c_ID","$$conversation_id"]},
            {"$ne":["$fromID","4"]},
            {"$gt":[
              {"$size":{
                "$filter":{
                  "input":"$$members",
                  "cond":{
                    "$cond":[
                      {"$gt":["$$this.l",null]},
                      {"$and":[
                        {"$gte":["$t","$$this.j"]},
                        {"$lte":["$t","$$this.l"]}
                      ]},
                      {"$gte":["$t","$$this.j"]}
                    ]
                  }
                }
              }},
              0
            ]}
          ]
        }
      }},
      {"$sort":{"t":-1}},
      {"$limit":1}
    ],
    "as":"overviewMessage"
  }}
])

如果你喜欢使用

events:[{ "join": 1581863346, "left": 1581863348},{ "join": 1581863349, "left": 1581863355}...]

您可以使用以下查询

db.conversations.aggregate(
[
  {"$match":{"members.uID":"4"}},
  {"$lookup":{
    "from":"messages",
    "let":{
      "member":{
        "$arrayElemAt":[
          {
           "$filter":{
            "input":"$members",
           "cond":{"$eq":["$$this.uID","4"]}
           }
          },
          0
        ]
      },
      "conversation_id":"$_id"
    },
    "pipeline":[
      {"$match":{
        "$expr":{
          "$and":[
            {"$eq":["$c_ID","$$conversation_id"]},
            {"$ne":["$fromID","4"]},
            {"$gt":[
              {"$size":{
                "$filter":{
                  "input":"$$member.events",
                  "cond":{
                    "$cond":[
                      {"$gt":["$$this.left",null]},
                      {"$and":[
                        {"$gte":["$t","$$this.join"]},
                        {"$lte":["$t","$$this.left"]}
                      ]},
                      {"$gte":["$t","$$this.join"]}
                    ]
                  }
                }
              }},
              0
            ]}
          ]
        }
      }},
      {"$sort":{"t":-1}},
      {"$limit":1}
    ],
    "as":"overviewMessage"
  }}
])

推荐阅读