Retrive array's first three element from document search based upon ID - node.js

I've an array inside document lets say user has followedBy array I want to find a user based on ID and then want to retrieve only first 3 elements of followedBy.
In picture you can see I have users collection and then inside user I've an array named as followedBy.
I need to search a user first based on Id and then to retrieve first 3 elements of array in a single query rather than fetch user first and get followedBy array having thousands of members inside it and then apply slice on it.

According to docs you can use $slice into projection.
So you have to do:
db.collection.find({
"_id": your_id
},
{
"followedBy": {
"$slice": 3
}
})
Check this example.
Using mongoose is exactly the same query:
YourModel.find({
"id": your_id
},
{
"followedBy": {
"$slice": 3
}
})
.then(result => {
// ...
}).catch(e => {})

Related

MongoDB: How to perform a second match using the results (an array of ObjectIds) of the previous match in aggregation pipeline

I have a MongoDB collection called users with documents that look like:
{
_id: ObjectId('123'),
username: "abc",
avatar: "avatar/long-unique-random-string.jpg",
connections: [ObjectId('abc'), ObjectId('xyz'), ObjectId('lmn'), ObjectId('efg')]
}
This document belongs to the users collection.
What I want to do:
First, find one document from the users' collection that matches _id -> '123'.
Project the connections field received from step 1, which is an array of ObjectIds of other users within the same collection.
Find all documents of users from the array field projected in step 2.
Project and return an array of only the username and avatar of all those users from step 3.
While I know that I can do this in two separate queries. First using findOne which returns the friends array. Then, using find with the results of findOne to get all the corresponding usernames and avatars.
But, I would like to do this in one single query, using the aggregation pipeline.
What I want to know, is it even possible to do this in one query using aggregation?
If so, what would the query look like?
What, I currently have:
await usersCollection
.aggregate([
{ $match: { _id: new ObjectId(userId) } },
{ $project: { ids: "$connections" } },
{ $match: { _id: { $in: "ids" } } },
{
$project: {
username: "$username",
avatar: { $ifNull: ["$avatar", "$$REMOVE"] },
},
},
])
.toArray()
I know this is wrong because each aggregation stage receives the results from the previous stage. So, the second match cannot query on the entire users' collection, as far as I know.
I'm using MongoDB drivers for nodeJS. And I would like to avoid $lookup for possible solutions.

Mongoose find array item in stored array

I have a document that stores an array of ids referencing a different collection:
{
title: 'Title of the document',
categories: [
{ _id: 1 },
{ _id: 2 },
{ _id: 3 },
{ _id: 4 },
]
}
Now I want to be able to find all the documents that have at least one of the categories that I pass into an array:
categories: [1, 3, 4]
I have searched stackoverflow and the MongoDB documentation, but I didn't find how to implement this.
I think the $in operator is what you are looking for. It selects documents where the value of a field equals any value in the specified array. Mongo documentation has a helpful page for it with a section for querying values in an array.
You will also need to use dot notation to query for the _id field of the categories array. The docs also have a page for querying an array of embedded documents, which is what you are doing here (you are querying an array of category documents).
For your case, your query object should probably be something like
{ 'categories._id' : { $in: [1, 3, 4] } }

Mongodb check if value is in a nested array

I have a collection in my database that contains a field which is composed of 3 arrays, like this :
use_homepage: {
home: [Array],
hidden: [Array],
archive: [Array]
}
This field represents the homepage of a user.
Each array contains an ObjectID that identifies projects shown on the user homepage.
I would like to check if my project id is in use_homepage.home or use_homepage.hidden, and if it is, remove the id from the array that match.
Can I do this with 1 (or 2 max) requests or do I have to make a request each time I have to check in another array ?
In case you expect to update one document at most, you can try this:
db.entities.findAndModify({
query: { $or : [
{ home: ObjectId('<HERE YOUR ID TO BE FOUND>') },
{ hidden: ObjectId('<HERE YOUR ID TO BE FOUND>') }
]},
update: { $pull: {
home: ObjectId('<HERE YOUR ID TO BE DELETED>'),
hidden: ObjectId('<HERE YOUR ID TO BE DELETED>')
}
}
});
As you can see, in general, you can search for some value and delete some other value.
The statement returns the original matching document (i.e. before the deletion is performed). If you want the modified document you can add the following attribute:
new: true
In case you search for many documents to update, this solution does not work, since findAndModify() works just on the first document matching the query condition.
Finaly, i used to make 2 requests to do the job :
db.User.find({"use_homepage.home": id}, {_id: 1}).toArray(function(err, result) {
// If some users have the id in the array home
db.User.updateMany({_id: {$in: users_match_ids}}, {
$pull: {"use_homepage.home": id}
}
});
// Do the same with 'hidden' array
If anyone see this post and have a better solution, I take it :)

Query/Find MongoDB documents with series of multiple conditions

I have a User schema with basic fields which include interests, location co-ordinates
I need to perform POST request with a specific UserId to get the results
app.post('api/users/search/:id',function(err,docs)
{ //find all the documents whose search is enabled.
//on documents returned in above find the documents who have atleast 3 common interests(req.body.interests) with the user with ':id'
// -----OR-----
//find the documents who stay within 'req.body.distance' compared to location of user with':id'
//Something like this
return User
.find({isBuddyEnabled:true}),
.find({"interests":{"$all":req.body.interests}}),
.find({"_id":req.params.id},geoLib.distance([[req.body.gcordinates],[]))
});
Basically i need to perform find inside find or Query inside query..
As per your comments in the code you want to use multiple conditions in your find query such that either one of those condition is satisfied and returns the result based on it. You can use $or and $and to achieve it. A sample code with conditions similar to yours is given below.
find({
$or:[
{ isBuddyEnabled:true },
{ "interests": { "$all":req.body.interests }},
{ $and:[
{ "_id":req.params.id },
{ geoLib.distance...rest_of_the_condition }
]
}
]
});

MongoDB (Node JS), how to correctly add calculated fields to a query result, where the calculated field uses a passed variable

I have a collection with feeds. The documents are structured something like this:
{
_id: '123',
title: 'my title',
openedBy: ['321', '432', '543'] // ID of users
}
Then I have users:
{
_id '321',
friends: ['432'] // ID of users
}
What I would like to accomplish is to get the number of friends that has opened the feeds fetched by the user. I do this now with a mapReduce, passing the friends of the user fetching the feeds. I do not think I am doing it correctly as I reduce by only returning the emit itself and I have to convert the result back to a normal query result on the finalizer:
db.collection(collectionName).mapReduce(function () {
var openedByFriendsLength = 0;
for (var x = 0; x < friends.length; x++) {
if (this.openedBy.indexOf(friends[x]) >= 0) {
openedByFriendsLength++;
}
}
emit(this._id, {
title: this.title,
openedByLength: this.openedBy.length,
openedByFriendsLength: openedByFriendsLength
});
}, function (key, emits) {
return emits[0];
}, {
out: 'getFeeds',
scope: {
friends: user.friends
},
}, function (err, collection) {
collection.find().toArray(function (err, feeds) {
// Convert the _id / value to a normal find result
var resultFeeds = [];
for (var x = 0; x < feeds.length; x++) {
resultFeeds.push(feeds[x].value);
resultFeeds[resultFeeds.length - 1]._id = feeds[x]._id;
}
callback(err, resultFeeds);
});
});
I have looked at aggregation, but I can not quite figure out how to do the same thing. Or is the structure of the documents here all wrong?
Thanks for any reply!
You ask how to do the calculation using the aggregation framework. In general the aggregation framework performs better than map-reduce. You can find documentation on the Aggregation Framework here: http://docs.mongodb.org/manual/aggregation/.
I understand that the calculation you want is, given a user, to find all feeds where that user is contained in the openedBy array, and then find the number of distinct friends of that user that are contained in those openedBy arrays. Do I have that correct?
Aggregation, like map-reduce, only operates on one collection at a time, so the first step is to obtain the list of friends for the user from the users collection, for example:
friends = db.users.findOne({_id:user}).friends
Then we can perform the following aggregation on the feeds collection to do the calculation:
db.feeds.aggregate([
{$match: {openedBy: user}},
{$unwind: '$openedBy'},
{$match: {openedBy: {$in: friends}}},
{$group: {_id: '$openedBy'}},
{$group: {_id: 0, count: {$sum: 1}}}
])
The aggregate command specifies a list of processing steps that work much like a Unix pipeline, passing streams of documents from one stage of the pipeline to the next.
The first first step in the pipeline, $match, takes as input all documents in the collection and selects only those where the user is contained in the openedBy array.
The second step, $unwind, takes each input document and produces multiple output documents, one for each member of the openedBy array; each output document contains an openedBy field whose value is a single user. These will be users that opened the same feeds as the given user. This step will allow later steps of the pipeline to perform aggregation operations on the indivdual values of the openedBy array.
The third step, $match, filters those documents to pass only the ones where the openedBy user is a friend of the given user. However a given friend may be represented more than once in this stream, so aggregation will be needed to eliminate the duplicates.
The fourth step, $group, performs an aggregation, generating one output document for each value of the openedBy field. This will be the set of unique friends, without duplication, of the given user who have opened a feed that the user opened. The _id field will be the friend user id.
The final step, another $group, counts the number of documents generated by the preceding step. It outputs a single document, with an _id of 0 (you could use any value you want here), and with a count field that contains the final count that you wished to calculate, for example:
{ "result" : [ { "_id" : 0, "count" : 2 } ], "ok" : 1 }
I hope this answer is helpful! Let me know if you have further questions.
Bruce

Resources