【发布时间】: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 概念与您的字段名称
left和join混淆了。为清楚起见,您可能需要考虑更改字段名称(仅适用于本文)。 -
如果您实际使用“活动”位,这可能会更简单,即让
j和l成为数组,当用户加入时,将新日期推送到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