mongodb - 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
- 上午 10:00:人 A 加入对话,但还看不到任何消息
- 上午 10:05:B 发布消息
- 上午 10 点 07 分:C 人发了一条消息
- 上午 10:10:A 发布消息
- 上午 10:12:B 发布消息
- 上午 10:15:A 离开对话
- 上午 10:20:C 人发了一条消息
此时,我希望 A 能够在他的概览中看到对话 X,其中来自 B(上午 10:12)的消息作为最后一条消息。当我进入对话时,我希望将上午 10:00 到上午 10:15 之间的所有消息显示给 A 人。所以只有来自 C 人(上午 10:20)的最后一条消息不应该显示。
- 上午 10:30:B 发布消息
- 上午 10:32:A 再次加入对话
- 上午 10:35:C 发布消息
- 上午 10:37:A 发布消息
- 上午 10 点 38 分:C 人发了一条消息
此时,我希望 A 人在他的概述中看到来自 C 人的最新消息。在对话本身中,我想显示上午 10:00 到 10:15 之间的消息,以及上午 10:32 到 10:38 之间的消息。
我只是想知道如何构建我的数据以及如何查询概述(基于我上面的查询)来实现这一点。稍后我会处理对话并为某个用户显示所有相关消息。
解决方案
您可以尝试以下聚合。
在该$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"
}}
])
推荐阅读
- rest - 如何在 Confluence Rest Api 创建的页面中插入一个 confluence 宏?或者如何创建一个带有宏附件的页面?
- php - 如何将刀片视图的列时间属性更改为 AM 和 PM?
- python - 如何从 python3.7 云函数将 Excel 文件写入 gcs 存储?
- javascript - codewars Array.diff javascript
- c# - 如何使单元测试方法更短
- c++ - 在 C++ 中查找数组中的元素总数
- android - 使用 RoomDatabase 使用 Retrofit 获取数据获取错误
- regex - 正则表达式:提取 XML(有角度的)标签之间的单个单词
- jenkins - Jenkins JNLP 远程处理 - 频繁的 ping 超时
- azure - 如何使用analyzeForm power 技能将数值数据索引到天蓝色搜索?