$or doesn't work for subdocument level search - node.js

Search retrieves result only in parent level. search with $or condition returns empty array for subdocument level filter/search. $or doesn't work for subdocument level search. Any fixes for this or any mistakes in my code?
exports.getAllProduct = (req, res, next) => {
categories.find({$or:[{“categoryName”:{ $regex: req.params.id, $options: ‘i’ }},
{“subcategory.$.productName”:{ $regex: req.params.id, $options: ‘i’ }}]},
(err, products) => {
console.log(“Products:“+JSON.stringify(products));
if (err) {
return res.status(400).json({
error: “No Products found”+err
});
}
res.status(200).json(products)
});
};
const categorySchema = new mongoose.Schema({
categoryName: {
type: String,
trim: true
},
subcategory: [ {
subcategoryName: {
type: String,
trim:true
},
productCode: {
type: String,
trim: true
}]});
http://localhost:3000/category/getAllProduct/fruits
What mistake why $or is not working? I'm using mongoDB 4.4.1 and mongoose 5.10.8 and Mac OS Catalina.

As I can see sub-category is an array..you should check in the second parameter of the $or if the input is “$in” array.
Check this post
MongoDB query IN array of object

If I'm reading your mongo query correctly, you have a $ in subcategory.$.subcategoryName which is most likely causing the issue you are seeing. Use subcategory.subcategoryName to query against that value.
See below:
categories.find({
$or:[
{
“categoryName”:{
$regex: req.params.id,
$options: 'i'
}
},
{
“subcategory.subcategoryName”: {
$regex: req.params.id,
$options: 'i'
}
}
]},
(err, products) => {}
);
Also, always try to format your code cleanly as in this example. It makes finding solutions easier.
If you do not need the query to be case insensitive the below query will be cleaner:
categories.find({
$or:[
{
“categoryName”: req.params.id
},
{
“subcategory.subcategoryName”: req.params.id
}
]},
(err, products) => {}
);

Related

Node.js Express case-insensitive search (filter) through MongoDB database

I want to make case-insensitive search API (I am using Express, Mongoose and Angular). I have datatable in my Angular application and Input field. So my API should return me data by (onChange). I have two collections (containers and containerstypes).
I want it to work exactly the same as this example:https://www.w3schools.com/jquery/jquery_filters.asp?fbclid=IwAR3klbA6BJQ_a3wTRf8legaucd4S_2Ns6j8QGQjElgVCrEbde6HT3DSZz38
This search API returns me the right data fields (owner, identificationNo and manufacturer, which is in separate collection, but I sucessfully get it from other collection). But this API which I listed down returns me data when i write FULL STRING, it doesn't work by writing letters.
router.get("/search", async (req, res) => {
try {
const { searchString } = req.body;
const containers = await Container.aggregate([
{
$lookup: {
from: "containerstypes",
localField: "containerTypeID",
foreignField: "_id",
as: "containerTypes",
},
},
{ $unwind: "$containerTypes" },
{
$match: {
$or: [
{ owner: searchString },
{ IdentificationNo: searchString },
{ "containerTypes.manufacturer": searchString },
],
},
},
]);
res.status(200).json(containers);
} catch (err) {
res.status(404).json({ success: false, msg: "Container not found" });
}
});
Thanks everyone for help. I used here aggregation, but if it is possible, I can make it without aggregation framework. For only listing data in my table i used find and populate functions.
It is better to create a text index on the fields you are searching on.
in your model file you can create the index this way,
schema.index({owner: 'text', IdentificationNo: 'text'});
schema.index({'containerTypes.manufacturer': 'text'});
for searching use the $text and the $search operators,
await Container.find({$text: {$search: searchString }});
The issue here is that you can't use $text inside the aggregation function, it is only allowed as the first stage in the pipeline which is not useful for your case.
I would suggest embedding the containersType collection inside the containers collection if possible
I found a solution. I just included $regex before every SearchString. For now it works, but I would appreciate, since I don't have much real world experience, if someone could tell me, if this is good solution or no.
router.get("/search", async (req, res) => {
try {
const { searchString } = req.body;
const containers = await Container.aggregate([
{
$lookup: {
from: "containerstypes",
localField: "containerTypeID",
foreignField: "_id",
as: "containerTypes",
},
},
{ $unwind: "$containerTypes" },
{
$match: {
$or: [
{ owner: { $regex: searchString, $options: "i" } },
{ IdentificationNo: { $regex: searchString, $options: "i" } },
{
"containerTypes.manufacturer": {
$regex: searchString,
$options: "i",
},
},
],
},
},
]);
res.status(200).json(containers);
} catch (err) {
res.status(404).json({ success: false, msg: "Container not found" });
}
});

remove a particular array element from mongodb on clicking the element

I'm using this code for removing a particular array element stored in MongoDB when clicked that from the app. But this code is not working.
schema structure looks like this -
const tagsSchema = new Schema({
category: {
type: String,
required: true
},
tname: { type: Array }
}, { _id: true });
Below is the code I'm using for removing array element from db -
Tags.updateOne({ tname: req.params.name }, { $pull: { _id: [req.params.id] } })
For example - "tname": "technical", "nontechnical"
Now, technical is being clicked in the app to remove, but with the code I'm using it's not getting removed.
you can use directly your element_value without [] after your array field, like below..
Tags.updateOne({tname: req.params.name}, { $pull: { your_array_field: req.params.id } } )
You have to find the specific tag by its "_id" and then remove the particular name from the "tname" array.
Tags.updateOne({ _id: req.params.id }, { $pull: { tname: req.params.name } })

How do i $set and $push in one update MongoDB?

I'm trying to $push and $set at the same time, $push is working just fine, when it comes to $set, it generates this error:
MongoError: The positional operator did not find the match needed from
the query. Unexpanded update: files.$.name
Here's the code
Course.update(
{
_id: req.body.courseId,
'files.fileUrl': { $ne: url }
},{
$push: { files: { fileUrl: url } },
$set: {'files.$.name': file.name},
}, function(err, count) {
if (err) return next(err);
console.log("Successfully saved")
});
and the ORM model, I'm using mongoose
var CourseSchema = new Schema({
files: [{
fileUrl: String,
name: { type: String, default: 'File name'}
}]
});
Any help would be appreciated. Thanks.
As the error states looks like the query used is returning no documents or returning documents having no files[].
Another reason for which it might be throwing error is that you're trying to $push & $set in the same field files and probably running into an issue similar to https://jira.mongodb.org/browse/SERVER-1050
IMHO, there is no good reason to use the same field in $push & $set, instead you can simply change
$push: { files: { fileUrl: url } },
$set: {'files.$.name': file.name},
to
$push: { files: { fileUrl: url, name: file.name } },
I have written similar kind of query for my project
Hope u could relative this to your scenario
exports.candidateRating = function(req, res) {
console.log(req.query);
console.log(req.body.RoundWiseRatings);
Profiles.update({
"name": req.query.name
}, {
$set: {
"ratings": req.body.ratings,
},
$push: {
"RoundWiseRatings": req.body.RoundWiseRatings
}
}, {
multi: true
}, function(error, profiles) {
if (error) {
}
return Profiles.find({
name: req.query.name
}, function(err, profiless) {
console.log(profiless);
if (err) {
return handleError(res, err);
}
return res.status(200).json(fnStruncturedData(profiless[0].RoundWiseRatings));
});
});};
And this worked for me :)

Creating a array element in mongoose

This is my schema.
team_year: String,
team: [{
_leader: String,
_member:[{
_name: String,
_phone: String,
_age: Number,
_gender: String,
_comment:[{
_date: String,
_contents: String,
_attendance: Boolean
}]
}]
}]
I have data
{ team_year: 2015
team: [
{
_leader: tom
_member: [
{_name: mike,
_phone: 2222
]
},
{
_leader:jack,
_member: []
}
]
}
I want to register a team member of Jack.
team_schema.findOneAndUpdate(
{team_year: '2015', 'team._leader' : 'jack'},
{$push: {
'team._member': req.body
}
},
function(err, post){
if (err) next(err);
res.end("success");
});
but it doesn't work.
Please help me.
I use
Node.js + express + MongoDB
I'm not good at English. T^T
You need to specify the index of the object for which you want to insert an object (nested array). For this you can use the positional operator ('$') provided by MongoDB. See here for more info.
So this query should work:
team_schema.findOneAndUpdate(
{team_year: '2015', 'team._leader' : 'jack'},
{$push: {
'team.$._member': req.body //The $ symbol resolves to an index from the query
}
},
function(err, post){
if (err) next(err);
res.end("success");
});

Get result as an array instead of documents in mongodb for an attribute

I have a User collection with schema
{
name: String,
books: [
id: { type: Schema.Types.ObjectId, ref: 'Book' } ,
name: String
]
}
Is it possible to get an array of book ids instead of object?
something like:
["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]
Or
{ids: ["53eb797a63ff0e8229b4aca1", "53eb797a63ff0e8229b4aca2", "53eb797a63ff0e8229b4aca3"]}
and not
{
_id: ObjectId("53eb79d863ff0e8229b97448"),
books:[
{"id" : ObjectId("53eb797a63ff0e8229b4aca1") },
{ "id" : ObjectId("53eb797a63ff0e8229b4acac") },
{ "id" : ObjectId("53eb797a63ff0e8229b4acad") }
]
}
Currently I am doing
User.findOne({}, {"books.id":1} ,function(err, result){
var bookIds = [];
result.books.forEach(function(book){
bookIds.push(book.id);
});
});
Is there any better way?
It could be easily done with Aggregation Pipeline, using $unwind and $group.
db.users.aggregate({
$unwind: '$books'
}, {
$group: {
_id: 'books',
ids: { $addToSet: '$books.id' }
}
})
the same operation using mongoose Model.aggregate() method:
User.aggregate().unwind('$books').group(
_id: 'books',
ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
// use res[0].ids
})
Note that books here is not a mongoose document, but a plain js object.
You can also add $match to select some part of users collection to run this aggregation query on.
For example, you may select only one particular user:
User.aggregate().match({
_id: uid
}).unwind('$books').group(
_id: 'books',
ids: { $addToSet: '$books.id' }
}).exec(function(err, res) {
// use res[0].ids
})
But if you're not interested in aggregating books from different users into single array, it's best to do it without using $group and $unwind:
User.aggregate().match({
_id: uid
}).project({
_id: 0,
ids: '$books.id'
}).exec(function(err, users) {
// use users[0].ids
})

Resources