Mongoose failing with $in: [number array] - node.js

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)
}
})

Related

Mongoose multiple optional 'and' conditions

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)] } },
}
],
})

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();
});

Push into a List and Pop Conditionally in Mongoose and MongoDB

I am creating a list of scores for a user in mongoDB by adding a new score 1 at a time and sorting the list. I want to remove the lowest score when the list grows larger than 5 elements.
The reason for this is because I want to store the top 5 scores of the user.
What would be the best way to do this? Is there a way to make the whole thing an atomic operation?
My code is below. I'm using NodeJS with Mongoose and MongoDB.
const maxScoresToStore = 5
var scoreEntrySchema = new Schema({
score: Number,
when: { type: Date, default: Date.now }
})
var scoreSchema = new Schema({
_userid: { type: Schema.Types.ObjectId, ref: 'Users' },
username: {type: String, index:{unique: true}},
scores: [scoreEntrySchema]
})
const scoreModel = mongoose.model("Scores", scoreSchema)
exports.addUserScore = (uid, uname, score) => {
var query = {_userid:uid, username:uname},
update = { $push : {"scores" : {$each: [{"score": score}], $sort: {"score":-1}}} }, // sorts in descending order after pushing
options = { upsert: true, new: true, setDefaultsOnInsert: true };
scoreModel.findOneAndUpdate(query, update, options).then(
(result)=>{
if(result.scores.length > maxScoresToStore)
{
// ToDo:
// result.update({$pop: {"scores" : 1 }}) // pops the last element of the list
}
}
)
}
You can use $slice operator, And your query looks like:
let score = await scoreModel.findOneAndUpdate({ _userid: uid, username: uname },
{
$push: {
scores: {
$each: [{ score: score }],
$sort: { score: -1 },
$slice: maxScoresToStore
}
}
},
{
upsert: true,
new: true,
setDefaultsOnInsert: true,
multi: true
});
[DO VOTE TO THIS ANSWER, IF ITS HELPFUL TO YOU]
You can add slice option to your update option:
update = {
$push: {
scores: { $each: [{ score: score }], $sort: { score: -1 }, $slice: maxScoresToStore }
}
}
Here is the full method code written in async/await style:
exports.addUserScore = async (uid, uname, score) => {
const query = { _userid: uid, username: uname };
const update = {
$push: {
scores: {
$each: [{ score: score }],
$sort: { score: -1 },
$slice: maxScoresToStore
}
}
};
const options = {
upsert: true,
new: true,
setDefaultsOnInsert: true,
multi: true
};
try {
let score = await scoreModel.findOneAndUpdate(query, update, options);
if (!score) res.send(404).send("Score not found");
res.send("Everything is ok");
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
};
I'm not certain If this would help, but it might work
scoreModel.update(
{ "scores.5": { "$exists": 1 } },
{ "$pop": { "scores": 1 } },
{ "multi": true }
)
As you are already sorting by descending, you can check if the array length is greater than 5 by using scores.5, If this returns true then you can pop the last element using $pop.
If $exists return false then it will skip the query. you can run this update after .then() and you won't have to use if condition.
But keep in mind $pop will only remove 1 element.

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
});
});

Updating multiple sub-documents with Mongoose and Node

I have a Model wich contains an array of sub-documents. This is a Company:
{
"_id" : ObjectId(":58be7c236dcb5f2feff91ac0"),
"name" : "sky srl",
"contacts" : [
{
"_id" : ObjectId("58be7c236dcb5f2feff91ac2"),
"name": { type: String, required: true },
"company" : ObjectId("58be7c236dcb5f2feff91ac0"),
"email" : "sky#gmail.com",
"chatId" : "",
"phone" : "123456789",
"name" : "John Smith"
},
{
"_id" : ObjectId("58be7f3a6dcb5f2feff91ad3"),
"company" : ObjectId("58be7f3a6dcb5f2feff91ad1"),
"email" : "beta#gmail.com",
"chatId" : "",
"phone" : "987654321",
"name" : "Bill Gaset"
}
],
"__v" : 1
}
I have several companies, and I want to update the field chatId of all the contacts in all the companies, that matches the phone I am searching for.
My Schema definitions (simplified, for focusing on question):
var contactSchema = new Schema({
[...]
phone: { type: String, required: true },
email: { type: String },
chatId: { type: String },
company: Schema.Types.ObjectId,
});
var companySchema = new Schema({
name: { type: String, required: true },
type: { type: String, default: "company" },
contacts: [contactSchema]
});
I tried
var conditions = { "contacts.phone": req.body.phone };
var partialUpdate = req.body; //it contains 'req.body.phone' and 'req.body.chatId' attributes
Company.find(conditions).then(
function (results) {
results.map( function(companyFound) {
companyFound.contacts.forEach(function (contactContainer){
if (contactContainer.phone == partialUpdate.phone) {
contactContainer.chatId = partialUpdate.chatId;
Company.save();
companyFound.save();
contactContainer.save();
results.save();
}
//not sure of what to save, so i save everything
companyFound.save();
contactContainer.save();
results.save();
});
});
});
following this answer; but it doesn't works. It does not save anything, what I'm doing wrong?
I have never done this before, but worth a try.
Maybe you need to use $elemMatch.
// find the companies that have contacts having the phone number
Company.find().where('contacts', { $elemMatch: { phone: req.body.phone }}).exec(function (err, companies) {
if (err) {
console.log(err);
return;
}
// see if you can at least get the query to work
console.log(companies);
async.eachSeries(companies, function updateCompany(company, done) {
// find and update the contacts having the phone number
company.contacts.forEach(function (contact, i, arr) {
if (contact.phone == req.body.phone) {
arr[i].chatId = req.body.chatId;
}
});
company.save(done);
}, function allDone (err) {
console.log(err);
});
});
Note, I am using async.js to do async operations on multiple items.
Honestly, I would have simply made contacts an array of Contact references -- much easier to query and update.
Just for the records: I did this to make it work without async.js:
Company.find().where('contacts', { $elemMatch: { phone: req.body.phone } })
.exec(function (err, companies) {
if (err) {
console.log(err);
return;
}
console.log("companies: " + JSON.stringify(companies, null, 4));
companies.forEach(function (company) {
company.contacts.map(function (contact, i, arr) {
if (contact.phone == req.body.phone) {
arr[i].telegramChatId = req.body.telegramChatId;
}
});
company.save();
},
function allDone(err) {
console.log(err);
});
});`

Resources