I have 2 schemas:
var UserSchema = new Schema({
login: {type:String, unique: true, trim:true, index:true},
password: String
)};
var MessageSchema = new Schema({
text: String,
from_id: {type:mongoose.Schema.ObjectId, ref: 'user', index:true},
to_id: {type:mongoose.Schema.ObjectId, ref: 'user', index:true},
date: Number
});
Now for example I have 4 users in the database. Assuming user1 sent 5 messages to each other user, user2 and user3 each replied to user1 message. Now I need to return only one recent message from each dialog. How can i do this?
One excellent way to do this would be by using MongoDB's aggregation framework.
I'm not 100% clear on your specific use-case, but I would think that using $unwind
to get all of the messages in the same place, and then filtering by perhaps a $setUnion
for uniqueness, then a $sort
and a $limit
if you want it to only display the most recent messages.
Here's a link to the quick reference: http://docs.mongodb.org/manual/meta/aggregation-quick-reference/
Hope this helps, and good luck!
Everything done here is mongo console you will have to translate to your driver yourself
If you want to query for specific conversations add a conversation_id
to your message (maybe the usernames combined or just a random string) then you can aggregate with
db.messages.aggregate({$sort:{date:-1}},
{$group:{_id:"$conversation_id", message:{$first:"$$ROOT"}})
Or just embed you messages in a conversation document like David K. seems to have thought you where already doing.
I build an aggregate that does what you want but you may want to go with the easy way after you see this (maybe someone can think of a shorter one).
First the collection modeled after your schema:
> db.chat.find().pretty()
{ "from" : 1,
"to" : 2,
"text" : "First from 1 to 2",
"date" : 1}
{ "from" : 2,
"to" : 1,
"text" : "First answer from 2 to 1",
"date" : 2}
{ "from" : 1,
"to" : 2,
"text" : "Second from 1 to 2",
"date" : 3}
{ "from" : 1,
"to" : 3,
"text" : "First 1 to 3",
"date" : 3}
{ "from" : 3,
"to" : 1,
"text" : "Reply 3 to 1",
"date" : 4}
_id
stipped for readbility and as you see two conversations, one between users 1
and 2
and one between 1
and 3
.
Now the aggregate query:
db.chat.aggregate({$project: {to:1,from:1,date:1,text:1,duplicator:{$const: [0,1]}}},
{$unwind:"$duplicator"},
{$group:{_id:"$_id",
parts:{$push:{$cond:[{$eq:"$duplicator",0]},"$from","$to"]}},
messages:{$addToSet: {text:"$text",date:"$date"}}}},
{$unwind:"$parts"},
{$sort:{parts:1}},
{$group:{_id:"$_id",
parts:{$push:"$parts"},
messages:{$first:"$messages"}}},
{$sort:{"messages.date":-1}},
{$group:{_id:"$parts", last_message:{$first:"$messages"}}})
Output:
{ "_id" : [1,2],
"last_message" : [{ "text" : "Second from 1 to 2",
"date" : 3 }]
}
{ "_id" : [1,3],
"last_message" : [{"text" : "Reply 3 to 1",
"date" : 4}]
}
The last two parts ($sort
and $group
) are the same as in the easy way. The rest is to build a conversation_id
named parts
for participants. This is done by
duplicator
to
and from
in an array parts
for each message. parts
will work a conversation_idThose steps above are inspired by this answer
parts
$sort
them because [1,2] != [2,1]
$group
them into one array againTose steps result in a conversation id which can then be used like proposed in the two line aggreate above.
Build you schema according to the way you want to query it because raping mongodb with strange aggregates is only fun until it kicks you in the balls in self defense...
If you just want the last message of a specific conversation you could use
db.chat.find({to:{$in:[ user1,user2]},from:{$in:[user1,user2]}})
.sort({date:-1})
.limit(1)