Checking for common data inside an array of objects using mongoose - javascript

My mongoose Scheme is as follows
Project: {
name: String,
stat: Number,
users: [
{user_id: String}
]
}
So each project contains multiple users. I am provided with two user ids and I need to find documents which contain both of those ids. Tried using elemmatch but can't figure out correct syntax. Please suggest ways to find documents like these.
This is the code I tried
Project.find({
users: {
$elemMatch: { id: req.query.id1 },
$elemMatch: { id: req.query.id2 }
}
})
but this returns all project which contains any one of the ids, not both.

So my original code was OK, it was not running due to some minor errors in other areas.
Project.find({
users: {
$elemMatch: { id: req.query.id1 },
$elemMatch: { id: req.query.id2 }
}
})
This is the code. I'll keep it here in case someone faces the same problem

Related

Trouble With Mongoose ETL Process and Deeply Nested Array

I'm working on building a database from a set of CSV files and am running into issues pushing elements to a deeply nested array. I've seen examples for 2D arrays and the use of positional operators but I can't quite figure out how to use them for my situation.
What I am trying to do read the CSV file which has columns for the answer_id and the url string for the photo. I want to push the url string to the photos array for the corresponding answer_id. When I try to use the code below, I get a long error message which starts with:
MongoBulkWriteError: Cannot create field 'answers' in element
{
results: [
{
question_id: "1",
_id: ObjectId('6332e0b015c1d1f4eccebf4e'),
answers: [
{
answer_id: "5",
//....
}
],
}
]
}
I formatted the above message to make things easier to read. It may be worth noting the first row of my CSV file has '5' in the answer_id column which makes me think things are failing at the first try to update my document.
Here is an example of my code:
const ExampleModel = new Schema({
example_id: String,
results: [
{
question_id: String,
answers: [
{
answer_id: String,
photos: [
{ url: String },
]
}
]
}
]
});
// Example Operation
// row.answer_id comes from CSV file
updateOne: {
'filter': {
'results.answers.answer_id': row.answer_id,
},
'update': {
'$push': {
'results.answers.$.photos' : { 'url': 'test'}
}
}
}
I guess my question is can I update an array this deeply nested using Mongoose?

Mongoose: find document by values inside an array field

I've got a chat schema that looks like that:
var chatSchema = new mongoose.Schema({
users: [{
type: mongoose.Schema.Types.ObjectId,
required: true
}]
});
It contains array of user IDs.
Now I want to find one chat document that contains an array of two user IDs.
At the beginning I tried to do this:
Chat.findOne({ users: { $in: [req.user_id, receiver._id] }})
.then(chat => { })
But it seems that every time it gives me the chat that contains at least one of the IDs I mentioned in the query.
So I've tried to change it to this but with no luck:
Chat.findOne()
.where({ users: { $in: [req.user_id] }})
.where({ users: { $in: [receiver._id] }})
.then(chat => { })
I need to find the chat that contains both of the user ID's inside the users array otherwise I expect for a null value.
How can I achieve this goal?
Thanks!
This is the way $in works - returns the document when at least one value matches. You should use $all instead:
Chat.findOne({ users: { $all: [req.user_id, receiver._id] }})

MongoDB: Updating array item in collection is not working

I have a structure like this:
{
...
_id: <projectId>
en-GB: [{
_id: <entryId>,
key: 'some key',
value: 'some value',
}]
}
And I've tried updating it with Mongoose (and raw mongodb too) like this:
const result = await Project
.update({
_id: projectId,
'en-GB._id': entryId,
}, {
$set: {
'en-GB.$.key': 'g000gle!!',
},
})
.exec();
I've checked that the IDs are correct. But it doesn't update anything:
{ n: 0, nModified: 0, ok: 1 }
What am I doing wrong? Thanks
As discussed in the comments on the question, the issue is directly related to passing in a string representation of an id in the query as opposed to using an ObjectId. In general, it's good practice to treat the use of ObjectIds as the rule and the use of string representations as special exceptions (e.g. in methods like findByIdAndUpdate) in order to avoid this issue.
const { ObjectId } = require('mongodb');
.update({
_id: ObjectId(projectId),
'en-GB._id': ObjectId(entryId),
})

Querying a collection from ObjectId's with $ne and $or condition

I need to serve an unanswered (by user) question to the user where user model holds the questions asked in past with an array, say it user.asked.
I want to clarify that I promisified mongoose library, hence I'm using those functions, nothing fancy.
User model:
var UserSchema = new Schema({
:
asked: [{ type: Schema.Types.ObjectId, ref: 'Question' }]
})
Here's what I tried so far:
Question.findAsync({
_id: {
$ne: {
$or: req.user.asked
}
}
})
which results: Cast to ObjectId failed for value \"[object Object]\" at path \"_id\".
I also tried to make an aggregation (example given):
{
$match: {
_id: {
$ne: {
$or: [ ObjectId("56ccfb048f896e0c2d06d08f"), ObjectId("56ccfb048f896e0c2d06d98f") ]
}
}
}
}
however this returns all of the documents in the collection (even the referred ones).
Do you have any suggestions?
You can use the $nin operator to find a doc with a field that matches none of the values in an array:
Question.findAsync({
_id: {
$nin: [ObjectId("56ccfb048f896e0c2d06d08f"), ObjectId("56ccfb048f896e0c2d06d98f")]
}
})

MongoDB query on populated fields

I have models called "Activities" that I am querying for (using Mongoose). Their schema looks like this:
var activitySchema = new mongoose.Schema({
actor: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
recipient: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
timestamp: {
type: Date,
default: Date.now
},
activity: {
type: String,
required: true
},
event: {
type: mongoose.Schema.ObjectId,
ref: 'Event'
},
comment: {
type: mongoose.Schema.ObjectId,
ref: 'Comment'
}
});
When I query for them, I am populating the actor, recipient, event, and comment fields (all the references). After that, I also deep-populate the event field to get event.creator. Here is my code for the query:
var activityPopulateObj = [
{ path: 'event' },
{ path: 'event.creator' },
{ path: 'comment' },
{ path: 'actor' },
{ path: 'recipient' },
{ path: 'event.creator' }
],
eventPopulateObj = {
path: 'event.creator',
model: User
};
Activity.find({ $or: [{recipient: user._id}, {actor: {$in: user.subscriptions}}, {event: {$in: user.attending}}], actor: { $ne: user._id} })
.sort({ _id: -1 })
.populate(activityPopulateObj)
.exec(function(err, retrievedActivities) {
if(err || !retrievedActivities) {
deferred.reject(new Error("No events found."));
}
else {
User.populate(retrievedActivities, eventPopulateObj, function(err, data){
if(err) {
deferred.reject(err.message);
}
else {
deferred.resolve(retrievedActivities);
}
});
}
});
This is already a relatively complex query, but I need to do even more. If it hits the part of the $or statement that says {actor: {$in: user.subscriptions}}, I also need to make sure that the event's privacy field is equal to the string public. I tried using $elemMatch, but since the event has to be populated first, I couldn't query any of its fields. I need to achieve this same goal in multiple other queries, as well.
Is there any way for me to achieve this further filtering like I have described?
The answer is to change your schema.
You've fallen into the trap that many devs have before you when coming into document database development from a history of using relational databases: MongoDB is not a relational database and should not be treated like one.
You need to stop thinking about foreign keys and perfectly normalized data and instead, keep each document as self-contained as possible, thinking about how to best embed relevant associated data within your documents.
This doesn't mean you can't maintain associations as well. It might mean a structure like this, where you embed only necessary details, and query for the full record when needed:
var activitySchema = new mongoose.Schema({
event: {
_id: { type: ObjectId, ref: "Event" },
name: String,
private: String
},
// ... other fields
});
Rethinking your embed strategy will greatly simplify your queries and keep the query count to a minimum. populate will blow your count up quickly, and as your dataset grows this will very likely become a problem.
You can try below aggregation. Look at this answer: https://stackoverflow.com/a/49329687/12729769
And then, you can use fields from $addFields in your query. Like
{score: {$gte: 5}}
but since the event has to be populated first, I couldn't query any of its fields.
No can do. Mongodb cannot do joins. When you make a query, you can work with exactly one collection at a time. And FYI all those mongoose populates are additional, distinct database queries to load those records.
I don't have time to dive into the details of your schema and application, but most likely you will need to denormalize your data and store a copy of whatever event fields you need to join on in the primary collection.

Categories

Resources