Mongoose multiple optional 'and' conditions - node.js

I am trying to filter a collection with 3 optional 'and' conditions.
Here is my model:
const Company = mongoose.model(
'Company',
new Schema({
name: { type: String },
sectors: [{ type: Schema.Types.ObjectId, ref: 'Sector' }],
industries: [{ type: Schema.Types.ObjectId, ref: 'Industry' }],
countries: [{ type: Schema.Types.ObjectId, ref: 'Country' }],
})
And my component:
const getCompanies = (skip, limit, filter) =>
Company.find({
...filter.countries && { countries: filter.countries },
...filter.sectors && { sectors: filter.sectors },
...filter.industries && { industries: filter.industries },
})
.skip(skip)
.limit(limit)
.sort({ date: -1 })
.populate('countries')
.populate('sectors')
.populate('industries');
const getAll = async (req, res) => {
try {
const countries = req.query.country;
const sectors = req.query.sector;
const industries = req.query.industry;
const skip = parseInt(req.query.skip, 10);
const limit = parseInt(req.query.limit, 10);
const filter = {
...countries && { countries },
...sectors && { sectors },
...industries && { industries },
};
const result = await getCompanies(skip, limit, filter);
return res.status(200).json(result);
} catch (e) {
return res.status(500).send({ message: (e.message) });
}
};
This is working when the filter is empty, but when there is one or more items in the filter, I get an empty array.
If I hard code data in getCompanies like so:
Company.find({
countries: '5d5e913e20c01070fef5c77e',
sectors: '5d5e913e20c01070fef5c754',
industries: '5d5e913e20c01070fef5c7ad',
})
or :
Company.find({
countries: '5d5e913e20c01070fef5c77e'
})
I get the data I want.
I also tried to console.log the filter in getCompanies to make sure the data was correct, and I get this if all fields are requested:
{
countries: '5d5e913e20c01070fef5c77e',
sectors: '5d5e913e20c01070fef5c754',
industries: '5d5e913e20c01070fef5c7ad',
}
and this for just one:
{ countries: '5d5e913e20c01070fef5c77e' }
So it seems fine to me.
I also tried using '$and' like so:
Company.find({ $and: [
{ ...filter.countries && { countries: filter.countries } },
{ ...filter.sectors && { sectors: filter.sectors } },
{ ...filter.industries && {industries: filter.industries } },
],
})
or using '$in':
Company.find({
...filter.countries && { countries: { $in: filter.countries } },
...filter.sectors && { sectors: { $in: filter.sectors } },
...filter.industries && { industries: { $in: filter.industries } },
})
But no luck either.
Here is a sample URL:
GET /api/internal/member/get?skip=12&limit=6&country=5d5e913e20c01070fef5c77e&sector=&industry=
I have found some other threads with questions somewhat similar to mine, but they were to different to help me solve my case.
Looking forward to your helpful advice.

I finally got it to work. Turns out I was not clearing the data in the frontend at each change, which caused an issue with the skip/limit fields.
I also changed the find as advised by #whoami like so:
Company.find({ $and: [
{
...filter.countries && { countries: { $in: [mongoose.Types.ObjectId(filter.countries)] } },
...filter.sectors && { sectors: { $in: [mongoose.Types.ObjectId(filter.sectors)] } },
...filter.sdgs && { sdgs: { $in: [mongoose.Types.ObjectId(filter.sdgs)] } },
}
],
})

Related

Mongoose failing with $in: [number array]

MongoDb documents looks like this:
{
"_id": {
"$oid": "60abf5ffc4b1cb61e05bdc48"
},
"club": "FC Midtjylland",
"number": 6,
"__v": 0,
"name": "Joel Andersson"
}
If I create a filter in MongoDB Compass like:
{number: { $in: [6,11] } }
I get all documents with number 6 and 11
In mongose my schema is like:
const Player = new Schema ({
club: { type: String, required: true },
name: { type: String, required: true },
number: { type: Number, required: true },
image: { type: String, required: false },}, { collection: 'players' });
If I try to find the same documents in node.js with mongoose i get no results:
var test = "6,11"
Player.find({number: { $in: [test] }}, function (err, player) {
if (!player || player.length <= 0) {
console.log('No player found!')
ws.send('No player found!');
}
else {
//ws.send(player.number+','+player.name+','+player.image);
console.log(player)
}
})
If I input the numbers directly in the query everything is working fine like:
Player.find({number: { $in: [6,11] }}, funct
What can I do to solve this?
try this:
var test = [6,11]
Player.find({number: { $in: test }}, function (err, player) {
if (!player || player.length <= 0) {
console.log('No player found!')
ws.send('No player found!');
}
else {
//ws.send(player.number+','+player.name+','+player.image);
console.log(player)
}
})

check an array of string value with array of object in mongodb

I have array of strings like this
let fromHour = ['2.5','3','3.5']
let toHour = ['2.5','3','3.5']
I have an array of object saved in mongoDB
timeRange = [
{
from:'2.5',
to:'3'
},
{
from:'3',
to:'3.5'
}
]
I want to check if any of my array of string value exist in that object value
I have tried this but it give me this error ( Unrecognized expression '$match' )
checkAppoint = await Appointment.aggregate([
{
$project: {
date: myScheduleFinal[k].date,
status: { $in: ['pending', 'on-going'] },
timeRange: {
'$match': {
'from': { $in: fromHolder },
'to': { $in: toHolder },
},
},
},
},
]);
also I have tried this solution and it work for me but it take to much time so I am trying this with aggregate
checkAppoint = await Appointment.findOne({
date: myScheduleFinal[k].date,
status: { $in: ['pending', 'on-going'] },
timeRange:{$elemMatch:{
from:{$in:fromHolder},
to:{$in:toHolder}
}}
});
So anyone have a solution for that
Just try $elemMatch and $in operators,
using find() method
checkAppoint = await Appointment.find({
timeRange: {
$elemMatch: {
from: { $in: fromHour },
to: { $in: toHour }
}
}
})
Playground
using aggregate() method
checkAppoint = await Appointment.aggregate([
{
$match: {
timeRange: {
$elemMatch: {
from: { $in: fromHour },
to: { $in: toHour }
}
}
}
}
])
Playground
So I have found a way around to solve this problem and I will share the solution I used
First I want to minimize my request to mongodb so I am now making just one request that bring all the appointment with the required date
and I want to make it this way because my fromHour and toHour array will change many time through single request
helperArray => contains all the day I want to check it's range
let checkAppoint = await Appointment.find({
date: { $in: helperArray },
status: { $in: ['pending', 'on-going'] },
});
now inside my for loop I will go through that data
checkAppoint.filter((singleAppoint) => {
if (singleAppoint._doc.date === myScheduleFinal[k].date) {
singleAppoint._doc.timeRange.map((singleTime) => {
if (fromHolder.includes(singleTime.from)) {
busy = true;
}
});
}
});

Mongodb update multiple documents with different values

I have been trying to use updatemany with mongoose. I want to update the values in database using an array of objects.
[
{
"variantId": "5e1760fbdfaf28038242d676",
"quantity": 5
},
{
"variantId": "5e17e67b73a34d53160c7252",
"quantity": 13
}
]
I want to use variantId as filter.
Model schema is:
let variantSchema = new mongoose.Schema({
variantName: String,
stocks: {
type: Number,
min: 0
},
regularPrice: {
type: Number,
required: true
},
salePrice: {
type: Number,
required: true
}
})
I want to filter the models using variantId and then decrease the stocks.
As you need to update multiple documents with multiple criteria then .updateMany() wouldn't work - it will work only if you need to update multiple documents with same value, Try this below query which will help you to get it done in one DB call :
const Mongoose = require("mongoose");
let variantSchema = new mongoose.Schema({
variantName: String,
stocks: {
type: Number,
min: 0
},
regularPrice: {
type: Number,
required: true
},
salePrice: {
type: Number,
required: true
}
})
const Variant = mongoose.model('variant', variantSchema, 'variant');
let input = [
{
"variantId": "5e1760fbdfaf28038242d676",
"quantity": 5
},
{
"variantId": "5e17e67b73a34d53160c7252",
"quantity": 13
}
]
let bulkArr = [];
for (const i of input) {
bulkArr.push({
updateOne: {
"filter": { "_id": Mongoose.Types.ObjectId(i.variantId) },
"update": { $inc: { "stocks": - i.quantity } }
}
})
}
Variant.bulkWrite(bulkArr)
Ref : MongoDB-bulkWrite
I don't think this can be done with a single Model.updateMany query. You will need to loop the array and use Model.update instead.
for (const { variantId, quantity } of objects) {
Model.update({ _id: variantId }, { $inc: { stocks: -quantity } });
}
To run this in a transaction (https://mongoosejs.com/docs/transactions.html), the code should look something like this (however I have not tried or tested this):
mongoose.startSession().then(async session => {
session.startTransaction();
for (const { variantId, quantity } of objects) {
await Model.update({ _id: variantId }, { $inc: { stocks: -quantity } }, { session });
}
await session.commitTransaction();
});

populating more fields of mongoose collection causing delay in response time in node js api

I have written api in node js with mongoose in which a collection have lot of foreign fields references. Populating them is needed for client side and it is causing delay in response time.
I have tried fetching only those documents of other collections which are referenced in main collection.
Following is the piece of code in which Lead is main collection and it contains references of other collections like project, list, assignee. I am trying to populate documents of these references along with leads collection data.
return db.model('Lead').aggregate([
{
$facet: {
totalData: [
{ $match: queryObj },
{ $group: { _id: { email: "$email", phone: "$phone" }, count: { $sum: 1 }, leads: { $push: { _id: "$_id", name: "$name", firstName: "$firstName", lastName: "$lastName", email: "$email", project: "$project", list: "$list", assignee: "$assignee", phoneCode: "$phoneCode", phone: "$phone", createdAt: "$createdAt", updatedAt: "$updatedAt", utmSource: "$utmSource", source: "$source", unreadmembers: "$unreadmembers" } } } },
{ $sort: sortObj },
{ $skip: (page - 1) * count },
{ $limit: count }
],
totalCount: [
{ $match: queryObj },
{ $group: { _id: { email: "$email", phone: "$phone" } } },
{ $count: "leadsCount" }
]
}
}
]).collation({ locale: "en" }).allowDiskUse(true).exec(async (err, leadsData) => {
if (err)
return res.apiError('Failed to get leads!');
else {
let leads = (leadsData[0] && leadsData[0].totalData && leadsData[0].totalData.length) ? leadsData[0].totalData : [];
let leadsCount = (leadsData[0] && leadsData[0].totalCount && leadsData[0].totalCount.length && leadsData[0].totalCount[0] && leadsData[0].totalCount[0].leadsCount) ? leadsData[0].totalCount[0].leadsCount : 0;
let leadsNew = leads.map(obj => obj.leads);
leadsNew = [].concat(...leadsNew);
let assignees = {};
let assigneeIds = leadsNew.filter(ele => ele.assignee && (ele.assignee !== '' || ele.assignee !== null)).map(l => l.assignee);
assigneeIds.forEach(_id => (assignees[_id.toString()] = _id));
let assigneesList = Object.values(assignees);
assigneesList = await db.model('User').find({ _id: { $in: assigneesList } }, { email: 1 }).lean();
let projects = {};
let projectIds = leadsNew.filter(ele => ele.project && (ele.project !== '' || ele.project !== null)).map(l => l.project);
projectIds.forEach(_id => (projects[_id.toString()] = _id));
let projectsList = Object.values(projects);
projectsList = await db.model('Project').find({ _id: { $in: projectsList } }, { name: 1 }).populate({ path: 'teams', select: { name: 1, members: 1 } }).lean();
let leadLists = {};
let listIds = leadsNew.filter(ele => ele.list && (ele.list !== '' || ele.list !== null)).map(l => l.list);
listIds.forEach(_id => (leadLists[_id.toString()] = _id));
let lists = Object.values(leadLists);
lists = await db.model('List').find({ _id: { $in: lists } }, { name: 1 }).lean();
leads.map(l => {
l.leads.map(p => {
if (p.assignee)
p.assignee = assigneesList.find(a => a._id.toString() === p.assignee.toString()) || '';
if (p.project)
p.project = projectsList.find(a => a._id.toString() === p.project.toString()) || '';
if (p.list)
p.list = lists.find(a => a._id.toString() === p.list.toString()) || '';
})
})
return res.apiOk({ leads, count: leadsCount });
}
})
I want to reduce response time of this api. Any idea for solving this is welcome.

mongoDB find, update and pull in One Query

I want to do all the find the data from the collection and then want to update some field as well as depending on want to empty the array.
const addCityFilter = (req, res) => {
if (req.body.aCities === "") {
res.status(409).jsonp({ message: adminMessages.err_fill_val_properly });
return false;
} else {
var Cities = req.body.aCities.split(","); // It will make array of Cities
const filterType = { "geoGraphicalFilter.filterType": "cities", "geoGraphicalFilter.countries": [], "geoGraphicalFilter.aCoordinates": [] };
/** While using $addToset it ensure that to not add Duplicate Value
* $each will add all values in array
*/
huntingModel
.update(
{
_id: req.body.id,
},
{
$addToSet: {
"geoGraphicalFilter.cities": { $each: Cities }
}
},
{$set:{filterType}},
).then(function(data) {
res.status(200).jsonp({
message: adminMessages.succ_cityFilter_added
});
});
}
};
Collection
geoGraphicalFilter: {
filterType: {
type:String,
enum: ["countries", "cities", "polygons"],
default: "countries"
},
countries: { type: Array },
cities: { type: Array },
aCoordinates: [
{
polygons: { type: Array }
}
]
}
But as result, the only city array is getting an update. No changes in filterType.
You appear to be passing the $set of filterType as the options argument, not the update argument.
huntingModel
.update(
{
_id: req.body.id,
},
{
$addToSet: {
"geoGraphicalFilter.cities": { $each: Cities }
},
$set: {
filterType
}
}
).then(function(data) {
res.status(200).jsonp({
message: adminMessages.succ_cityFilter_added
});
});

Resources