Finding all matches of the object value inside chats array in mongodb - node.js

I am building a chat app and I am stuck at a query. I have chats stored inside an array of objects. I want to fetch chats matching the key of object value. I am using mongoose library to query mongodb. Please do help me in solving this.
My chat schema is:
chats: [
{
sentFrom: 'user1',
sentTo: 'athlete1',
message: 'hi'
},
{
sentFrom : 'user2',
sentTo: 'athlete1',
message: 'hi athleyte'
}
]
I want to find all the chats where sentFrom value is 'user1'.
This is my code and it's not working.

From your document ,
you are doing match on whole collection and your condition is chats.sentFrom should be equal to user1 that's why,
If any other document has similar kind of data in that case it will return 2 documents in order to filter out the array you will have to use $filter using aggregation
db.test.aggregate([
{ $match: {_id: ObjectId("5f87fec2e80b410a20c6819a")}},
{ $project: {
chats: {$filter: {
input: '$chats',
as: 'chat',
cond: {$eq: ['$$chat.sentFrom', "user1"]}
}}
}}
])

Try filter:
let result = chats.filter(chat => chat.sentFrom === 'user1');

Related

Mongo db - how to join and sort two collection with pagination

I have 2 collections:
Office -
{
_id: ObjectId(someOfficeId),
name: "some name",
..other fields
}
Documents -
{
_id: ObjectId(SomeId),
name: "Some document name",
officeId: ObjectId(someOfficeId),
...etc
}
I need to get list of offices sorted by count of documetns that refer to office. Also should be realized pagination.
I tryied to do this by aggregation and using $lookup
const aggregation = [
{
$lookup: {
from: 'documents',
let: {
id: '$id'
},
pipeline: [
{
$match: {
$expr: {
$eq: ['$officeId', '$id']
},
// sent_at: {
// $gte: start,
// $lt: end,
// },
}
}
],
as: 'documents'
},
},
{ $sortByCount: "$documents" },
{ $skip: (page - 1) * limit },
{ $limit: limit },
];
But this doesn't work for me
Any Ideas how to realize this?
p.s. I need to show offices with 0 documents, so get offices by documets - doesn't work for me
Query
you can use lookup to join on that field, and pipeline to group so you count the documents of each office (instead of putting the documents into an array, because you only case for the count)
$set is to get that count at top level field
sort using the noffices field
you can use the skip/limit way for pagination, but if your collection is very big it will be slow see this. Alternative you can do the pagination using the _id natural order, or retrieve more document in each query and have them in memory (instead of retriving just 1 page's documents)
Test code here
offices.aggregate(
[{"$lookup":
{"from":"documents",
"localField":"_id",
"foreignField":"officeId",
"pipeline":[{"$group":{"_id":null, "count":{"$sum":1}}}],
"as":"noffices"}},
{"$set":
{"noffices":
{"$cond":
[{"$eq":["$noffices", []]}, 0,
{"$arrayElemAt":["$noffices.count", 0]}]}}},
{"$sort":{"noffices":-1}}])
As the other answer pointed out you forgot the _ of id, but you don't need the let or match inside the pipeline with $expr, with the above lookup. Also $sortByCount doesn't count the member of an array, you would need $size (sort by count is just group and count its not for arrays). But you dont need $size also you can count them in the pipeline, like above.
Edit
Query
you can add in the pipeline what you need or just remove it
this keeps all documents, and counts the array size
and then sorts
Test code here
offices.aggregate(
[{"$lookup":
{"from":"documents",
"localField":"_id",
"foreignField":"officeId",
"pipeline":[],
"as":"alldocuments"}},
{"$set":{"ndocuments":{"$size":"$alldocuments"}}},
{"$sort":{"ndocuments":-1}}])
There are two errors in your lookup
While passing the variable in with $let. You forgot the _ of the $_id local field
let: {
id: '$id'
},
In the $exp, since you are using a variable id and not a field of the
Documents collection, you should use $$ to make reference to the variable.
$expr: {
$eq: ['$officeId', '$$id']
},

Search data from 2 or more collection using search keyword in mongodb with Nodejs

I am getting data from 2 collections using keyword search in the child collection from mongodb. I am using aggregate function to set the lookup and search from the lookup. I tried inside the pipeline with $regexMatch. But it is not working with $and condition. here is my code,
"$lookup": {
"from": "post_cloths",
let: { postId: '$_id', search: 'abc' },
pipeline: [
{
$match: {
$expr:
{
$regexMatch: {
input: "$category",
regex: /c/,
options: "i"
},
$and:
[
{ $eq: ["$postId", "$$postId"] },
// { $search: ["$cateogry", 'abc'] }
],
}
}
}
],
"as": "postCloth"
}
without $and it working fine..
The problem statement is -- I have 2 collections one is posts and another is postCloths. I need to return data from post collection along with lookup with postCloth with some search keword.
lookup will return the collection if any of the key is matched on that collection, like just a formal search from the database.
I am getting the data from the $and statement, but need to search all the keys along with the above $eq statement
Here the output I required-----
all data from the post collection which contains the keyword in postCloth. So I need multiple like (in terms of sql) statement in lookup statement. I mean search on multiple keys
Thanks in advance

how to do aggregation in nodejs

I have to fetch a record which have the name field that I give and a subjectId field of another collection for which I have the subjectname with me -- e.g., I would like to fetch a student record which have name lora and subjectname as physics (but the problem is I have subjectname with me but the collection student have the field id of that subject which is been already saved to some other collection say subjects --with _id ---subject _id act as foreign field in student record, I have student name and subject name with me, how can I access the student with corresponding fields?
You have two options on how to do this in Mongo, with option number 1 being the one I personally recommend as it's going to be more efficient.
Split this into two queries:
let subject = await subjectCollection.findOne({name: subjectname});
// add validation that subject exists in case needed.
let doc = await firstCollection.findOne({name: name, subjectId: subject._id})
// you can use `find` instead if multiple documents can be matched.
Use $lookup
firstCollection.aggregate([
{
$match: {
name: name
}
},
{
$lookup: {
from: "subject_collection",
let: {subjectName: subjectName},
pipeline: [
{
$match: {
$expr: {
$eq: ["$$subjectName", "$name"]
}
}
}
],
as: "subjects"
}
},
{
$match: {
"subjects.0": {$exists: true}
}
}
])

How to query using an array which matches with a single element in mongodb [duplicate]

If I have this schema...
person = {
name : String,
favoriteFoods : Array
}
... where the favoriteFoods array is populated with strings. How can I find all persons that have "sushi" as their favorite food using mongoose?
I was hoping for something along the lines of:
PersonModel.find({ favoriteFoods : { $contains : "sushi" }, function(...) {...});
(I know that there is no $contains in mongodb, just explaining what I was expecting to find before knowing the solution)
As favouriteFoods is a simple array of strings, you can just query that field directly:
PersonModel.find({ favouriteFoods: "sushi" }, ...); // favouriteFoods contains "sushi"
But I'd also recommend making the string array explicit in your schema:
person = {
name : String,
favouriteFoods : [String]
}
The relevant documentation can be found here: https://docs.mongodb.com/manual/tutorial/query-arrays/
There is no $contains operator in mongodb.
You can use the answer from JohnnyHK as that works. The closest analogy to contains that mongo has is $in, using this your query would look like:
PersonModel.find({ favouriteFoods: { "$in" : ["sushi"]} }, ...);
I feel like $all would be more appropriate in this situation. If you are looking for person that is into sushi you do :
PersonModel.find({ favoriteFood : { $all : ["sushi"] }, ...})
As you might want to filter more your search, like so :
PersonModel.find({ favoriteFood : { $all : ["sushi", "bananas"] }, ...})
$in is like OR and $all like AND. Check this : https://docs.mongodb.com/manual/reference/operator/query/all/
In case that the array contains objects for example if favouriteFoods is an array of objects of the following:
{
name: 'Sushi',
type: 'Japanese'
}
you can use the following query:
PersonModel.find({"favouriteFoods.name": "Sushi"});
In case you need to find documents which contain NULL elements inside an array of sub-documents, I've found this query which works pretty well:
db.collection.find({"keyWithArray":{$elemMatch:{"$in":[null], "$exists":true}}})
This query is taken from this post: MongoDb query array with null values
It was a great find and it works much better than my own initial and wrong version (which turned out to work fine only for arrays with one element):
.find({
'MyArrayOfSubDocuments': { $not: { $size: 0 } },
'MyArrayOfSubDocuments._id': { $exists: false }
})
Incase of lookup_food_array is array.
match_stage["favoriteFoods"] = {'$elemMatch': {'$in': lookup_food_array}}
Incase of lookup_food_array is string.
match_stage["favoriteFoods"] = {'$elemMatch': lookup_food_string}
Though agree with find() is most effective in your usecase. Still there is $match of aggregation framework, to ease the query of a big number of entries and generate a low number of results that hold value to you especially for grouping and creating new files.
PersonModel.aggregate([
{
"$match": {
$and : [{ 'favouriteFoods' : { $exists: true, $in: [ 'sushi']}}, ........ ] }
},
{ $project : {"_id": 0, "name" : 1} }
]);
There are some ways to achieve this. First one is by $elemMatch operator:
const docs = await Documents.find({category: { $elemMatch: {$eq: 'yourCategory'} }});
// you may need to convert 'yourCategory' to ObjectId
Second one is by $in or $all operators:
const docs = await Documents.find({category: { $in: [yourCategory] }});
or
const docs = await Documents.find({category: { $all: [yourCategory] }});
// you can give more categories with these two approaches
//and again you may need to convert yourCategory to ObjectId
$in is like OR and $all like AND. For further details check this link : https://docs.mongodb.com/manual/reference/operator/query/all/
Third one is by aggregate() function:
const docs = await Documents.aggregate([
{ $unwind: '$category' },
{ $match: { 'category': mongoose.Types.ObjectId(yourCategory) } }
]};
with aggregate() you get only one category id in your category array.
I get this code snippets from my projects where I had to find docs with specific category/categories, so you can easily customize it according to your needs.
For Loopback3 all the examples given did not work for me, or as fast as using REST API anyway. But it helped me to figure out the exact answer I needed.
{"where":{"arrayAttribute":{ "all" :[String]}}}
In case You are searching in an Array of objects, you can use $elemMatch. For example:
PersonModel.find({ favoriteFoods : { $elemMatch: { name: "sushiOrAnytthing" }}});
With populate & $in this code will be useful.
ServiceCategory.find().populate({
path: "services",
match: { zipCodes: {$in: "10400"}},
populate: [
{
path: "offers",
},
],
});
If you'd want to use something like a "contains" operator through javascript, you can always use a Regular expression for that...
eg.
Say you want to retrieve a customer having "Bartolomew" as name
async function getBartolomew() {
const custStartWith_Bart = await Customers.find({name: /^Bart/ }); // Starts with Bart
const custEndWith_lomew = await Customers.find({name: /lomew$/ }); // Ends with lomew
const custContains_rtol = await Customers.find({name: /.*rtol.*/ }); // Contains rtol
console.log(custStartWith_Bart);
console.log(custEndWith_lomew);
console.log(custContains_rtol);
}
I know this topic is old, but for future people who could wonder the same question, another incredibly inefficient solution could be to do:
PersonModel.find({$where : 'this.favouriteFoods.indexOf("sushi") != -1'});
This avoids all optimisations by MongoDB so do not use in production code.

multi condition in $match mongodb aggregate framework _ mongoose [duplicate]

This question already has answers here:
Moongoose aggregate $match does not match id's
(5 answers)
Closed 4 years ago.
I have multiple conditions for the query in my controller that I need path if exists.
condition 1 :
{ tags: mongoose.Types.ObjectId(req.params.tagId)}
condition 2:
{ reportedBy: { '$ne': req.user._id }} // if this video object reported dont show
condition 3:
{ owner: { '$ne': userblocks } } // userblocks is a array of objectIds
so this is my $match filter:
{
'$match':
_.isString(req.params.tagId) ?
{ tags: mongoose.Types.ObjectId(req.params.tagId),
reportedBy:{ '$ne': req.user._id}, owner: { '$ne': userblocks}}:
{ reportedBy:{ '$ne': req.user._id},owner: {'$ne': userblocks}}
},
I used ... spread operator if tagId passed to params.this condition works for tagId but other conditions are not working.
with #Anthony Winzlet hint I tried to :
{reportedBy:{ '$ne': mongoose.Types.ObjectId(req.user._id)},owner: {'$ne': userblocks}}
and userblocks is a list of objects, I checked the type of them and they are objects too.so no need to cast them to objectIds.
Try something like this:
let $match = {
reportedBy: { '$ne': mongoose.Types.ObjectId(req.user._id) },
owner: { '$nin': userblocks } // $nin when comparing against array of object ids
},
if(_.isString(req.params.tagId)) {
$match.tags = mongoose.Types.ObjectId(req.params.tagId)
}
Then just use $match in your aggregation pipeline or as part of whatever else parts of the pipeline you would have.
Things to note:
When comparing to _id mongoose.Types.ObjectId function should be used.
When comparing against an array $in or $nin are usually what you would want to use.
It seems in your _.isSting check logic you had reportedBy and owner in both scenarios so it seems to me some refactoring would not hurt.

Resources