I have 2 collections:
user
{
_id: 'user_id1',
username: 'user1',
}
{
_id: 'user_id2',
username: 'user2',
}
{
_id: 'user_id3',
username: 'user3',
}
inbox
{
_id: 'inbox_id1',
from: {_id: 'user_id1', username: 'user1'},
to: {_id: 'user_id2', username: 'user2'},
text: 'Hello there',
timestamp: new Date(),
}
{
_id: 'inbox_id2',
from: {_id: 'user_id1', username: 'user1'},
to: {_id: 'user_id2', username: 'user2'},
text: 'Trying again...',
timestamp: new Date(),
}
{
_id: 'inbox_id3',
from: {_id: 'user_id3', username: 'user3'},
to: {_id: 'user_id2', username: 'user2'},
text: 'You there?',
timestamp: new Date(),
}
Whenever a user goes into his inbox, I would like to show him the thread list, which should include a list of latest messages from each user. So basically I would like to get distinct documents (based on the from._id field), and only the latest document (based on timestamp field).
So my results for user2 should include only 2 documents (inbox_id2 and inbox_id3).
I know I need to use aggregation for it, but not sure how exactly.
I was able to solve it based on a similar question: MongoDB : Aggregation framework : Get last dated document per grouping ID
My solution looks like this:
db.inbox.aggregate([
{$match : {'to.username': 'user2'}},
{'$sort': {'from._id': 1, 'timestamp': -1}},
{'$group': {
'_id': '$from._id',
'timestamp': {'$first': '$timestamp'},
'text': {'$first': '$text'},
'from': {'$first': '$from.username'},
}},
]);
Related
I have no idea about how to build a query which does this:
I have a collection of users, each user has a field userdata which contains an array of String.
Each string is the string of the ObjectID of other documents (news already seen) in another collection.
I need, knowing the username of this user, to perform a query which gets all the news but not those which have been already seen.
I think the $nin operator does what I need but I don't know how to mix it with data from another collection.
Users
user
username: String
userdata: Object
news: Array of String
News
news1
_id: ObjectID
news2
_id: ObjectID
EXAMPLE:
Users: [{
username: 'mario',
userdata: {
news: ['10', '11']
}
}]
News: [{
_id: '10',
content: 'hello world10'
},{
_id: '11',
content: 'hello world11'
},{
_id: '12',
content: 'hello world12'
}]
Passing to the query the username (as a String) 'mario', I need to query the collection News and get back only the one with _id '12'.
Thanks
You need to run $lookup with custom pipeline. There's no $nin for aggregations but you can use $not along with $in. Then you can also try $unwind with $replaceRoot to promote filtered News to the root level:
db.Users.aggregate([
{ $match: { username: "mario" } },
{
$lookup: {
from: "News",
let: { user_news: "$userdata.news" },
pipeline: [{ $match: { $expr: { $not: { $in: [ "$_id", "$$user_news" ] } } } }],
as: "filteredNews"
}
},
{ $unwind: "$filteredNews" },
{ $replaceRoot: { newRoot: "$filteredNews" }}
])
Given a collection of "purchases", how can I get each person's most recent purchase?
Each person is unique on first_name and last_name.
Example Collection:
[
{
first_name: "Don",
last_name: "Foobar",
date: 11111111, // The unix timestamp of purchase
purchase: {...}
},
{
first_name: "Don",
last_name: "Foobar",
date: 22222222,
purchase: {...}
},
{
first_name: "James",
last_name: "McManason",
date: 12341234,
purchase: {...}
}
...
]
What I've tried:
This below code would work (super sub-optimally) given a collection of the people's names to iterate through:
collection
.find({ first_name: "Tom", last_name: "Brady" })
.sort({ date: -1 })
.limit(1)
collection
.find({ first_name: "James", last_name: "Foo" })
.sort({ date: -1 })
.limit(1)
collection
.find({ first_name: "Marcia", last_name: "Bar" })
.sort({ date: -1 })
.limit(1)
So you need a more generic solution? If so then try this out:
db.collection.aggregate([
{ $sort: { date: -1}}, // sort by date descending
{
$group: {
_id: { firstName: "$first_name", lastName: "$last_name"}, // group by
// first and last name
purchase: {$first: "$purchase"} // get the first purchase since the documents are
// ordered by date and the first is also the latest
}
}
])
sorting through your complete collection though is not so efficient so you should consider adding a $match before the $sort.
I have a large collection called posts, like so:
[{
_id: 349348jf49rk,
user: frje93u45t,
comments: [{
_id: fks9272ewt
user: 49wnf93hr9,
comment: "Hello world"
}, {
_id: j3924je93h
user: 49wnf93hr9,
comment: "Heya"
}, {
_id: 30283jt9dj
user: dje394ifjef,
comment: "Text"
}, {
_id: dkw9278467
user: fgsgrt245,
comment: "Hola"
}, {
_id: 4irt8ej4gt
user: 49wnf93hr9,
comment: "Test"
}]
}]
My comments subdocument can sometimes be 100s of documents long. My question is, how can I return just the 3 newest documents (based on the ID) instead of all the documents, and return the length of all documents as totalNumberOfComments as a count instead? I need to do this for 100s of posts sometimes. This is what the final result would look like:
[{
_id: 349348jf49rk,
user: frje93u45t,
totalNumberOfComments: 5,
comments: [{
_id: fks9272ewt
user: 49wnf93hr9,
comment: "Hello world"
}, {
_id: j3924je93h
user: 49wnf93hr9,
comment: "Heya"
}, {
_id: 30283jt9dj
user: dje394ifjef,
comment: "Text"
}]
}]
I understand that this could be completed after MongoDB returns the data by splicing, although I think it would be best to do this within the query so that Mongo doesn't have to return all comments for every single post all the time.
Does this solve your problem? try plugging in the _id values and see what you are missing and post them here.
begin with this query
db.collection.aggregate([{$match: {_id: 349348jf49rk}},
{$project:{
_id:1,
user:1,
totalNumberOfComments: { $size: "$comments" },
comments: {$slice:3}
}
}
])
I'm having a problem that seems like it can be solved by some aggregation samples I've seen, but I've not come up with an answer yet.
Basically I have documents like so:
{
date: '2015-01-14 00:00:00.000Z',
attendees: ['john', 'jane', 'james', 'joanne'],
groupName: '31'
}
And I need to find the unique attendees for a groupName and their attendance count. So for example, with the data:
{
date: '2015-01-13 00:00:00.000Z',
attendees: ['john', 'jane', 'james', 'joanne'],
groupName: '31'
},
{
date: '2015-01-14 00:00:00.000Z',
attendees: ['james', 'joanne'],
groupName: '31'
},
{
date: '2015-01-15 00:00:00.000Z',
attendees: ['joanne'],
groupName: '31'
}
I'd like to get something like:
[{
name: 'joanne',
count: 3
}, {
name: 'john',
count: 1
}, {
name: 'james',
count: 2
}]
I can't seem to find an aggregation to get this type of result. Any help is appreciated.
you can do this:
db.collection.aggregate([
{$unwind: '$attendees'},
{$group: {_id: '$attendees', count: {$sum: 1}}},
{$project: {_id:0, name: '$_id', count: '$count'}}
])
I have the a document stored in mongodb:
shop: {
_id: '...'
title: 'my shop'
users: [
{
_id: '...',
name: 'user1',
username: '...'
},
{
_id: '...',
name: 'user2',
username: '...'
}
]
}
I use this query to get a subdocument user by his id:
Shop.findOne({'users._id': userId}, {'users.$': 1}, function (err, user) {
console.log(user);
});
Output:
{ _id: ...,
users:
[{
name: 'user1',
username: '...',
_id: ...
}]
}
How can I filter the result to only return the user name.
The way I do it now:
Shop.findOne({'users._id': userId}, {'users.$': 1}, function (err, shop) {
shop = shop.toObject()
user = shop.users[0]
filtered = {
name: user.name
}
callback(filtered);
});
But is there a better way to do it all in the query?
This question is almost two years old, but I noticed that people are still looking for a solution to this problem. fernandopasik's answer helped me very much, but is missing a code sample on how to use the suggested aggregation operations. That's why I post a more detailed answer.
The document I used is:
{
_id: '...'
title: 'my shop'
users: [
{
_id: 'user1Id',
name: 'user1',
username: '...'
},
{
_id: 'user2Id',
name: 'user2',
username: '...'
}
]
}
The solution I came up with (after reading the mongodb docs about aggregation) was:
Shop.aggregate([
{$unwind: '$users'},
{$match: {'users._id': 2}},
{$project: {_id: 0, 'name': '$users.name'}}
]);
To understand how the aggregation is working, it's best to try one operation at a time and read the mongodb docs of this operation.
Shop.aggregate([{$unwind: '$users'}])
$unwind deconstructs the users array (don't forget to include $ on the array name), so you end up with:
{
_id: '...',
title: 'my shop',
users: {
_id: 'user1Id',
name: 'user1',
username: '...'
}
}
{
_id: '...',
title: 'my shop',
users: {
_id: 'user2Id',
name: 'user2',
username: '...'
}
}
2. Using {$match: {'users._id': 'user2Id'}} on the aggregation pipeline (the two docs in this example) will return the whole document where users._id is 'user2Id':
{
_id: '...',
title: 'my shop',
users: {
_id: 'user2Id',
name: 'user2',
username: '...'
}
}
3. to return only name: 'user2' you can use {$project: {_id: 0, 'name': '$users.name'}}:
{name: 'user2'}
The aggregation pipeline is not easy to grasp at first. I recommend reading through the mongodb aggregation docs and try one aggregation operation at a time. It is sometimes hard to spot the error in the whole aggregation pipeline. Most of the time you simply get no result document from the pipeline when there is an error somewhere in the pipeline.
You should try mongodb aggregation framework with mongoose:
http://mongoosejs.com/docs/api.html#aggregate-js
I suggest you first apply unwind users and then match the user id and then project the username.