Query MongoDB using an object and array of ids - node.js

In searching through MongoDB data I've been able to query a collection based on whatever fields the user provides by adding any non-null field to a query object, which so far has covered all of my bases. But I've run into an issue with needing to add to it. Basically doing this for 10+ fields:
let query = {};
let weekNumber = null;
if (req.params.weekNumber !== 'noDataSent'){
weekNumber = req.params.weekNumber;
query.weekNumber = weekNumber;
}
...
Ticket.find(query)...
I've got two collections, User and Ticket. The Ticket objects contain an objectId ref to a User object. Since I can't query the Tickets by a users name due to the ref, I'm finding all users based on a provided name and then pulling out their _id to then add to the query object. In this case, if nothing else is specified I would like the result to only return the tickets that match any of the user _ids, and if there are other fields to work the same, to match everything else and find the tickets with the user ids.
I've tried using $or and $in, but unless I'm doing something wrong, I can't figure out how to make this work right.
Doing this returns an error, and I'm not even sure how to go about using $or in this case.
let users = [];
User.find( { $or: [ { firstname: requestor }, { lastname: requestor } ] } ).exec((err, user) => {
if (err){ return res.status.json({err: "No user(s) found"}); }
user.forEach((e) => {
users.push(new ObjectId(e._id));
});
});
Ticket.find(query, { requestor: { $in: users } } )
How can I match all of the fields in my query object and find all tickets with the user(s) _ids with this setup?
Edit:
I've tried doing this to get it to search through both, and this does search through the users correctly and brings back the tickets tied to them, it is ignoring everything in the query object.
Ticket.find({
$and:
[
{ query },
{requestor:
{ $in: users.map(doc => new ObjectId(doc._id))
}
}
]
})

You are running Ticket.find() outside of the callback of User.find(). While this is valid code, both of these queries are effectively queued up to go concurrently, with the the Ticket query utilizing the array of users - which the Users query has yet to populate.
In order to use the result of the Users query, you must put the next bit of code in that callback which can only be called after the query is complete:
User.find({
$or: [
{ firstname: requestor },
{ lastname: requestor }
]
}).exec((err, users) => {
if (err) return res.status.json({ err: "No user(s) found" });
Ticket.find({
requestor: {
$in: users.map(doc => new ObjectId(doc._id))
}
}).exec((err, tickets) => {
if (err) return res.status.json({ err: "No ticket(s) found" });
// do something with 'tickets'
tickets.forEach(doc => console.log(doc));
});
});

To solve my original question of how to search through the tickets using the query object I built up as well as use $in to find the tickets associated with the returned users, I came up with this solution that seems to work nicely.
query.requestor = { $in: users.map(doc => new ObjectId(doc._id)) };
Set the query. to the field I want to search through, in this case the requestor. That way I can simply do what I've been doing thus far and do a plain Ticket.find(query)

Related

Mongoose subdocument update: dot notation assignment OR .findByIdAndUpdate() [duplicate]

I am using .pull to remove a record from an array in mongo db and it works fine, but a comment I read somewhere on stack overflow (can't find it again to post the link) is bothering me in that it commented that it was bad to use .save instead of using .findByIdAndUpdate or .updateOne
I just wanted to find out if this is accurate or subjective.
This is how I am doing it currently. I check if the product with that id actually exists, and if so I pull that record from the array.
exports.deleteImg = (req, res, next) => {
const productId = req.params.productId;
const imgId = req.params.imgId;
Product.findById(productId)
.then(product => {
if (!product) {
return res.status(500).json({ message: "Product not found" });
} else {
product.images.pull(imgId);
product.save()
.then(response => {
return res.status(200).json( { message: 'Image deleted'} );
})
}
})
.catch(err => {
console.log(err);
});
};
I think what they were saying though was it should rather be done something like this (an example I found after a google)
users.findByIdAndUpdate(userID,
{$pull: {friends: friend}},
{safe: true, upsert: true},
function(err, doc) {
if(err){
console.log(err);
}else{
//do stuff
}
}
);
The main difference is that when you use findById and save, you first get the object from MongoDB and then update whatever you want to and then save. This is ok when you don't need to worry about parallelism or multiple queries to the same object.
findByIdAndUpdate is atomic. When you execute this multiple times, MongoDB will take care of the parallelism for you. Folllowing your example, if two requests are made at the same time on the same object, passing { $pull: { friends: friendId } }, the result will be the expected: only one friend will be pulled from the array.
But let's say you've a counter on the object, like friendsTotal with starting value at 0. And you hit the endpoint that must increase the counter by one twice, for the same object.
If you use findById, then increase and then save, you'd have some problems because you are setting the whole value. So, you first get the object, increase to 1, and update. But the other request did the same. You'll end up with friendsTotal = 1.
With findByIdAndUpdate you could use { $inc: { friendsTotal: 1 } }. So, even if you execute this query twice, on the same time, on the same object, you would end up with friendsTotal = 2, because MongoDB use these update operators to better handle parallelism, data locking and more.
See more about $inc here: https://docs.mongodb.com/manual/reference/operator/update/inc/

Model.findAll() only returns one of the same children

I have an application that uses Node, Postgres and Sequelize. Curently I have 3 models in my DB. User, Customer and Ticket.
Ticket has a n:n relation with Customer and User.
When I'm creating a new ticket I'm supposed to pass an array of two users, each with their type in the Ticket-User relation saved to the pivot table.
The problem happens when I try to set both users as the same user, meaning that in that ticket one user played the role of two. So when I try to retrieve this info, Sequelize only returns this user one time with one of the roles played, when in fact I wanted this user to be returned 2 times, each time with it's role.
Here is how I made the query to create a new Ticket (Note that the users field has the user with id 1, playing the OFFICE and INSPECTION role):
Here is how it is created using Sequelize:
const {
interested,
opening_date,
office_status,
inspection_status,
subject,
external_code,
conclusion_method,
obs,
users,
customer_id,
is_owner
} = req.body;
try {
const ticket = await Ticket.create({
interested,
opening_date,
office_status,
inspection_status,
subject,
external_code,
conclusion_method,
obs
});
if (users && users.length > 0) {
users.forEach(({ id, type }) => {
ticket.addUser(id, { through: { type } });
});
}
if (customer_id && is_owner) {
ticket.addCustomer(customer_id, { through: { is_owner } });
}
if (ticket) {
res.status(200).json({ MSG: "SAVE_SUCCESS" });
} else {
throw new Error({ err: "Ticket was not created" });
}
} catch (error) {
if (error.errors && error.errors.length >= 1) {
let errResponse = {};
error.errors.map(err => {
errResponse = { ...errResponse, [err.path]: err.message };
});
res.status(200).json({ err: errResponse });
return;
}
res.status(500).json(error);
}
}
And it works fine, if I manually check the table in the DB it shows the user with ID 1 listed twice, one time with each role.
The problem arises when I try to query it, here is how I am doing it:
const tickets = await Ticket.findAll({
include: [
{
model: User,
attributes: ["id", "fname", "lname", "username", "email"],
through: { model: TicketsUsers, attributes: ["type"] }
},
{
model: Customer,
attributes: ["id", "name", "document"],
through: { model: TicketsCustomer, attributes: ["is_owner"] }
}
]
});
if (tickets) {
res.status(200).json(tickets);
} else {
throw new Error({ err: "EMPTY" });
}
} catch (err) {
res.status(500).json(err);
}
It works when different users are assigned to each role, however when the same user is assigned to both roles, this is what gets returned:
As you can see, the users field only lists user with id 1 one time, using the role OFFICE, when it should have listed user with id 1 two times one with each role it played on that ticket.
Like I said this only happens when I assign the same user for both roles, if different users are used for each role, then the query returns both users in the usersfield as expected, which led me to believe the code is right, I even turned on Sequelize logging and manually executed the query it returned, and sure enough both users where returned on that query. So it is doing something internally that makes a user with same id only appear once.
I have found a similar issue here: https://github.com/sequelize/sequelize/issues/7541
However the problem there was a missing id on one of his tables, which is not my case. All my tables have unique IDs.
So anyone knows why is this happening and how to solve it?
Thank you.

How to update some data based on array value in Mongoose?

I'd like to update some data in Mongoose by using array value that I've find before.
Company.findById(id_company,function(err, company) {
if(err){
console.log(err);
return res.status(500).send({message: "Error, check the console. (Update Company)"});
}
const Students = company.students;
User.find({'_id':{"$in" : Students}},function(err, users) {
console.log(Students);
// WANTED QUERY : Update company = null from Users where _id = Students[];
});
});
Students returns users._id in array with object inside, and I use that to find users object, and then I want to set null a field inside users object, that field named as "company". How I can do that? Thank you.
From what you posted (I took the liberty to use Promises but you can roughly achieve the same thing with callbacks), you can do something like:
User.find({'_id':{"$in" : Students}})
.then( users =>{
return Promise.all( users.map( user => {
user.company = null;
return user.save()
}) );
})
.then( () => {
console.log("yay");
})
.catch( e => {
console.log("failed");
});
Basically, what I'm doing here is making sure .all() user models returned by the .find() call are saved properly, by checking the Promised value returned for .save()ing each of them.
If one of these fails for some reasons, Promise.all() return a rejection you can catch afterhand.
However, in this case, each item will be mapped to a query to your database which is not good. A better strategy would be to use Model.update(), which will achieve the same, in, intrinsically, less database queries.
User.update({
'_id': {"$in": Students}
}, {
'company': <Whatever you want>
})
.then()
use .update but make sure you pass option {multi: true} something like:
User.update = function (query, {company: null}, {multi: true}, function(err, result ) { ... });

Nodejs, Moongos find subdocument

I have this query ( mongoose)
user = {email:'my#email.com};
return this.company.get().findOne({
name: companyName,
users: { $elemMatch: user }
}).then((res: ICompany) => {
console.log(res.users);
}
The query is working.
The problem is that I was expecting one user in res.users (the one that match the $elemMatch), instead it returns all the users and to select my User I have to do a for-loop over all the users.
Is it possible to modify my query in the way that the res.users is populated with the user that match the email?
Thank you
Use of projection, here the documentation of mongodb 3.6
return this.company.get().findOne({
name: companyName,
users: { $elemMatch: user }
}, {
'users.$': 1,
}).then((res: ICompany) => {
console.log(res.users);
});

How to query for an array of users in my users collection in mongodb using mongoose?

I have a collection of some users (along with their contact numbers) registered in my node and mongodb app.
Now I have an array of contact numbers. I want to get an intersection of this array and the users in my app. How to do it, since I cannot take each number and check for it's existence in the database. Any help/link would be helpful. Thanks in advance.
You can use the $in operator. It would really help if you at least provided some code, like your schemas, previous attempts etc.
Here is an example of how to do ii assuming you are using mongoose, and have a user schema with a number property and an array of numbers.
User.find({ number: { $in: numbers } })
.then(function (docs) {
// do something with docs here;
})
.catch(handleErr);
This can be done simply using promises. I am assuming you have an array of contact numbers and a collection Users which has field contact number. The below function will get you the list of all the Users with contact numbers listed in the array. I am assuming that the User model is available and the contact array is being passed as a function argument. Basically this function will loop through the contact list array and find any user with that contact no in the user collection and return a promise. All the promises are being pushed into an array and the promise will be resolved only when all the async operations have completed resulting in either success or error.
// import User from User-model-defition
function getUsersByContact(contactArr) {
return new Promise((resolve, reject) => {
const data = [];
const errors = [];
contactArr.map(id => {
User.findOne({ contact_no : id })
.then(response => {
if (response.user) { //checking if the response actually contains a user object
data.push(response.user);
} else {
errors.push(response.error);
}
if (contactArr.length === data.length + errors.length) {
//this makes sure that the promise will be resolved only when all the promises have been resolved
resolve({ data, errors });
}
})
.catch(error => reject(error));
})
})
}

Resources