【问题标题】:MongoDB allow multiple join and left datesMongoDB 允许多个加入和离开日期
【发布时间】:2020-02-25 18:26:16
【问题描述】:

感谢 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 纪元 (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(时间戳),参加了两周然后离开(我将 l(左)字段设置为 1583685143(时间戳) .这一切都很好。但是,如何在 1 周后将用户 ID 4 再次添加到同一个群聊中(时间戳1584289943)并确保用户 ID 4 可以看到 lastMessage 如果它介于他第一次加入/离开了,还是在他再次加入后发布?**

我希望能够将同一用户多次添加到成员数组中,但使用不同的 j(和 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
        }
    ]
}

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

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

编辑:根据 Joe 的建议,我想采用如下所示的方法。有什么意见吗?我想知道如何使用下面的结构仅显示最后一个离开时间戳(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

  • 10:00 AM:A 加入对话,但还看不到任何消息
  • 10:05 AM:B 发布消息
  • 上午 10:07:人 C 发布消息
  • 上午 10:10:A 发布消息
  • 上午 10:12:B 发布消息
  • 10:15 AM:A 离开对话
  • 上午 10:20:C 人发了一条消息

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

  • 上午 10:30:B 发布消息
  • 10:32 AM:A 再次加入对话
  • 上午 10:35:人 C 发布消息
  • 上午 10:37:A 发布消息
  • 上午 10:38:人 C 发布消息

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

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

【问题讨论】:

  • 在阅读这篇文章时突然想到,我将一个名为 LEFT OUTER JOIN 的 RDBMS 概念与您的字段名称 leftjoin 混淆了。为清楚起见,您可能需要考虑更改字段名称(仅适用于本文)。
  • 如果您实际使用“活动”位,这可能会更简单,即让jl 成为数组,当用户加入时,将新日期推送到j 并设置@ 987654341@ 到 1,当他们离开时将新日期推送到 l 并将 a 设置为 0,那么查询只需匹配 {a:1} 即可查看要包含的内容。
  • @Joe 您所描述的正是我在添加活动字段时的想法。但是,我不确定我是否希望加入/离开时间戳在数组中,因为我想始终保持它们之间的关系。否则很难匹配多个时期,不是吗?关于如何设置该查询的任何示例或想法?
  • 存储它的最佳方式实际上取决于您打算如何使用数据。与其将它们存储在单独的字段中,不如存储一个事件数组,例如[{type:"join",date:ISODate()},{type:"leave",date:ISODate()},{type:"join",date:ISODate()}]。数组按日期排列,最后一个事件表示当前状态,对应的事件在数组中相邻。这只是众多可能性之一。
  • 有了关于如何查询的新信息,它实际上可能更适用于像[{join: 1581863346, leave: 1581963347},{join: 1581963348}] 这样的成员时间数组,然后您可以测试消息是否在用户是具有{events:{$elemMatch:{join:{$lte:msgtime},{leave:{$gte:msgtime}}}} 之类的成员

标签: mongodb performance mongodb-query


【解决方案1】:

您可以尝试以下聚合。

$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"
  }}
])

【讨论】:

  • 这似乎没有考虑到我数据库中的事件。看看最后一个结构,我在其中使用事件数组(加入/离开)来允许多个加入/离开日期。这对我来说是现在最重要的事情。如果您对如何更好地构建我的数据库有任何建议,请随时提出建议。我希望能够多次加入/离开,并检索我被允许查看的最后一条消息(因此它应该在加入/离开时间戳之间发送)。有什么想法吗?
  • 您可以尝试使用原始结构添加新条目吗?
  • 我不完全确定我目前保留加入和离开日期的方式(在“事件”下)是否有效。我不完全确定如何确保我可以在两个相关的加入/离开日期之间进行查询。但是,每个休假日期都应遵循每个定义的加入日期。但如果有办法确保它们总是“分组”,我想采用这种方法。
  • 嗯,@SagarVeeram 我来看看。所以每次有人加入时,我都需要在成员下添加一个新条目?然后我可以轻松检索所有成员,而不会出现重复的内容吗?我还想检索活跃成员的数量,所以我想是那些没有休假日期的成员?让我看看。
  • 只是一个想法。对于更新后的结构,更新条目而不是添加会更有意义。如果您想将它们分组,可以使用 events:[{ "join": 1581863346, "left": 1581863348},{ "join": 1581863349, "left": 1581863355}...] 之类的东西。
猜你喜欢
  • 2018-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多