我将完整的消息线程(带消息)存储为单个文档。数组字段participants
保存参与者用户ID。每条消息都有数组字段read_by
,其中包含读取该消息的用户ID。
示例数据:
db.threads_test.insert( { "subject" : "subject 1", "participants" : ["u1", "u2"], "messages" : [
{"message_id" : "m1", "message" : "msg 1", "read_by" : ["u1"]},
{"message_id" : "m2", "message" : "msg 2", "read_by" : ["u2"]}
]});
db.threads_test.insert( { "subject" : "subject 2", "participants" : ["u1", "u2"], "messages" : [
{"message_id" : "m3", "message" : "msg 3", "read_by" : ["u1"]},
{"message_id" : "m4", "message" : "msg 4", "read_by" : ["u1"]}
]});
db.threads_test.insert( { "subject" : "subject 3", "participants" : ["u1", "u3"], "messages" : [
{"message_id" : "m5", "message" : "msg 5", "read_by" : ["u1", "u3"]}
]});
我需要知道有多少未读线程以及用户拥有多少条未读消息。
解释逻辑是:
participants
数组中且位于的位置
用户不在read_by
participants
read_by
并且u1: threds=1, messages=1
u2: threads=2, messages=3
u3: threads=0, messages=0
每位用户的预期未读数量:
@media screen
我一直在检查聚合框架但找不到解决方案。
Mongo版本是2.4.9
答案 0 :(得分:0)
我认为你这样做是错误的结果(我认为我可能是错的)。
创建多个集合可能更好。为什么不让表格放threads
,messages
和user_read
:
Collection threads:
{thread_id: "...", subject: "...", participants: ["u1", "u2"], ...}
{thread_id: "...", subject: "...", participants: ["u1", "u3"], ...}
...
Collection messages:
{thread_id: "...", user_id: "...", message: "..."}
{thread_id: "...", user_id: "...", message: "..."}
...
Collection user_read:
{user_id: "u1", type: "thread", id: "..."}
{user_id: "u1", type: "message", id: "..."} # Care here the type is a message
...
现在您知道第一个集合中有X个线程(让我们称之为nbrThreads
)。并且您可以计算" u1"读取的线程数(使用type=='thread'
)。轻松地在user_read
(让我们称之为userThreadsRead
)。因此:
unreadThread = nbrThreads-userThreadsRead
通过在type=='message'
表上计算第二个集合和(使用user_read
)消息的相同逻辑。调用那些变量nbrMessages和userMessagesRead
unreadMessage = nbrMessages-userMessagesRead
另外,如果你不能改变结构,我建议你重新组织你的结构,以便能够做类似的事情。通过获取数组的长度,我应该可以对你的结构做同样的事情。
祝你好运!
答案 1 :(得分:0)
这对聚合框架来说并不容易,主要是由于有很多数组,所以有很多方法可以轻易搞错。幸运的是,当你仔细观察它时,这里有一个合理的逻辑模式,因为它只是归结为一个比较点:
db.threads_test.aggregate([
// Unwind all arrays
{ "$unwind": "$messages" },
{ "$unwind": "$messages.read_by" },
{ "$unwind": "$participants" },
// Group on distinct "message_id" comparing "particpant" and "read_by"
{ "$group": {
"_id": {
"_id": "$_id",
"participant": "$participants",
"message_id": "$messages.message_id"
},
"unread": {
"$min": {
"$cond": [
{ "$ne": [ "$participants", "$messages.read_by" ] },
1,
0
]
}
}
}},
// Get a sum of unread per thread
{ "$group": {
"_id": {
"_id": "$_id._id",
"participant": "$_id.participant",
},
"unread": { "$sum": "$unread" }
}},
// Sum per participant counting unread threads
{ "$group": {
"_id": "$_id.participant",
"threads": {
"$sum": {
"$cond": [
{ "$ne": [ "$unread", 0 ] },
1,
0
]
}
},
"unread": { "$sum": "$unread" }
}}
])
这会给你结果:
{ "_id" : "u2", "threads" : 2, "unread" : 3 }
{ "_id" : "u3", "threads" : 0, "unread" : 0 }
{ "_id" : "u1", "threads" : 1, "unread" : 1 }
第一个$group
阶段至关重要。在每个阵列上处理的Ater $unwind
将会有很多重复的复制。幸运的是每个级别的"线程"和"消息"有他们自己独特的" id"值。与独特的"参与者"他们自己这是至关重要的一点。
当您查看"解开"中的数据时形式,你应该能够看到所有重复的"关键测试"这里是比较"参与者"和" read_by"值以查看它们是否相同。就像"处理循环" (除了所有组合都没有列出),那么你只需要返回"一次"对于给定的消息,其中"参与者"和" ready_by"是"等于"。
这解释了"分组"组合。使用"键"由"线程","参与者"和" message_id"您只需要与" read_by"进行比较后的 $min
数值结果。所以,如果至少"一个" " read_by"已匹配,则计数为1
,否则为0
。
接下来的几个阶段只是为您的总计进行仔细分组。首先得到总数"未读"计算每个线程,然后将未读消息的线程计数到最终的"参与者"分组密钥。
所以虽然它不是"总是"找到解决方案的途径,在开始时执行所有$unwind
操作是一种可视化数据的好方法,这样您就可以理解解决方案。
正如您所说,您可以使用MongoDB 2.4,并且根据您的集合的大小,然后像这样处理$unwind
会导致很多开销。后来的版本对此有一些规定,但它可能是一个问题。
我之前提到"处理循环",这正是您可以使用mapReduce
做的事情。
虽然这里通常首选聚合框架,但如果大小是限制,则可能需要考虑这个:
db.threads_test.mapReduce(
function () {
var doc = this;
doc.participants.forEach(function(participant) {
doc.messages.forEach(function(message) {
var obj = {
threads: [],
unread: 0
};
if ( message.read_by.indexOf(participant) == -1 ) {
obj.threads.push(doc._id.valueOf());
obj.unread = 1;
}
emit(participant,obj);
})
})
},
function (key,values) {
var result = { "threads": [], "unread": 0 };
values.forEach(function(value) {
value.threads.forEach(function(thread) {
if ( result.threads.indexOf(thread) == -1 )
result.threads.push(thread);
})
result.unread += value.unread;
});
return result;
},
{
"finalize": function(key,value) {
value.threads = value.threads.length;
return value;
},
"out": { "inline": 1 }
}
)
真的在这里。通过每个消息的线程上的每个参与者,将它们与" read_by"列表,看看他们是否在那里。我们发出"线程ID"当邮件未读时,结果如果"未读"。与参与者一起为线程上的每条消息发出此消息。所以循环循环"。
结果是"减少"从"线程"中提取不同的值并且参与者总计未读消息。
因为"线程"是一个独特的列表" id"值,我们只想在减少后最终得到该列表的长度。这就是"最终确定"在这里做并将列表转换为它的长度的数值。
相同的结果,但不是很漂亮,因为这是mapreduce的限制:
"results" : [
{
"_id" : "u1",
"value" : {
"threads" : 1,
"unread" : 1
}
},
{
"_id" : "u2",
"value" : {
"threads" : 2,
"unread" : 3
}
},
{
"_id" : "u3",
"value" : {
"threads" : 0,
"unread" : 0
}
}
],
无论哪一个最适合你,问题解决方案现在应该对于程序性大脑和聚集大脑都是清楚的