Querying multiple sub-document elements in Mongoose - node.js

I can't seem to get the following query to work:
Group.find({ $or: [ {'groupOwner': req.user._id }, { 'subscribers.user': req.user._id, 'subscribers.level': 'owner' } ] }, 'groupName', { sort: ['groupName', 'ascending'] }, function(err, groups) {
My schema has a Group, with subscribers as a subdocument array. I want to find all groups where the groupOwner matches the passed user ID (works fine), and all documents where subscribers.user = req.user._id AND subscribers.level = 'owner'
The query as written returns all subdocuments where subscribers.user = req.user._id OR there is some subscriber with level = 'owner'. To be clear, I only want groups where subdocument has subscribers.user = req.user._id AND THE SAME SUB-DOCUMENT has subscribers.level = 'owner'.
I've tried all manner of $and and $elemMatch and just can't get it. Thanks for any help!

$elemMatch is the right operator when matching multiple fields in the same array element; your query should look like:
Group.find(
{ $or: [
{ groupOwner: req.user._id },
{ subscribers: { $elemMatch: { user: req.user._id, level: 'owner' }}}
]},
'groupName',
{ sort: ['groupName', 'ascending'] },
function(err, groups) { ...

Related

Mongoose FindOne returns all children

Is it ok that findOne returns all subdocument in array? I mean isn't it take a long time to execute that, or is there way to get document with only one subDoc ?
my code :
userModel
.find({
{_id: userId,},
{ contacts: { $elemMatch: { userId: contactId } } }
).select('name contacts');
I cant use userModel.contacts.id() cause I don't have id
How can I add a conversation id to this found document
How can I do
const userData = userModel.findById(userId)
.where('contacts')
.elemMatch({ userId: contactId })
and get the result with only one contact (in order not to search for a contact from the entire array)
and simply change relation like
userData.contacts[0].conversation = conversationId;
await userData.save()
How about this:
db.collection.update({
_id: 1
},
{
$set: {
"contacts.$[x].conversation": 3
}
},
{
arrayFilters: [
{
"x.userId": "John"
}
]
})
playground

Mongoose count certain element in an embedded documents array

I am using mongoose 4.6.3.
I have the following schema :
var mongoose = require('mongoose');
var User = require('./User');
var TicketSchema = new mongoose.Schema({
user : { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
},
{
timestamps: true
});
var DrawSchema = new mongoose.Schema({
...
max_ticket_per_user : { type : Number, required: true },
tickets: [TicketSchema]
});
module.exports = mongoose.model('Draw', DrawSchema);
How can I count the embedded documents of a certain User ObjectId(user field in TicketSchema) in a Draw's tickets(tickets field in DrawSchema) ?
I want to count the tickets of a user for a single draw.
Would it be better to change my schema design ?
Thanks
You can use the aggregation framework taking advantage of the $filter and $size operators to get the filtered array with elements that match the user id and its size respectively which will subsequently give you the count.
For an single draw, consider adding a $match pipeline operator as your initial step with the _id query to filter the documents in the collection.
Consider running the following aggregation pipeline to get the desired result:
Draw.aggregate([
{ "$match": { "_id": drawId } },
{
"$project": {
"ticketsCount": {
"$size": {
"$filter": {
"input": "$tickets",
"as": "item",
"cond": { "$eq": [ "$$item.user", userId ] }
}
}
}
}
}
]).exec(function(err, result) {
console.log(result);
});
You can pass the .count() deep parameters like any other query object:
Draw.count({'tickets.user._id' : userId}, console.log);
Make sure the userId variable is an ObjectId. If it's a string, do this:
const ObjectId = require('mongoose').Types.ObjectId;
let userId = new ObjectId(incomingStringId);

Mongoose updating sub document array's individual element(document)

Schema of group and member are as below:
var group=new Schema({
group_id:Number,
group_name:String,
members:[member]
});
var member=new Schema({
member_id:number,
name:String,
});
Sample document after inserting some record in group collection
[{
_id:55ff7fca8d3f6607114dc57d
group_id:1001,
group_name:"tango mike",
members:[
{
_id:44ff7fca8d3f6607114dc21c
member_id:2001,
member_name:"Bob martin" ,
address:String,
sex:String
},
{
_id:22ff7fca8d3f6607114dc22d
member_id:2002,
member_name:"Marry",
address:String,
sex:String
},
{
_id:44ff7fca8d3f6607114dc23e
member_id:2003,
member_name:"Alice" ,
address:String,
sex:String
}
]
}]
My problem:
I am trying to update record of individual group member(element of subdocument members). While updating I have follwing data group: _id, group_id, members:_id and newdata. I am trying like this; but it is not working
var newData={
member_name:"Alice goda" ,
address:"xyz",
sex:"F"
}
groupModel.findOne({"_id":"55fdbaa7457aa1b9bd7f7cf7","group_id":1001},'members -_id',function(err,groupMembers){
if(err)
{
res.json({
"isError":true,
"error":{
"status":1042,
"message":err
}
});
}
else
{
var mem=groupMembers.id("44ff7fca8d3f6607114dc23e");
mem.member_name=newData.member_name;
mem.address=newData.address;
mem.sex=newData.sex;
mem.save(function(err,data){
if(!err)
//sucessfull updated
});
res.json(groupDetails);
}
});
As I understand from your question details, you would like to update one object from the members array, in accordance with the criteria that you specify.
Thus, in order to accurately run the update query for your use case, you could run the following update operation against your collection:
db.collection.update({ _id: "55ff7fca8d3f6607114dc57d",
group_id:1001,
members: {
$elemMatch: { _id: "44ff7fca8d3f6607114dc23e" }
}
},
{ $set: {
"members.$.member_name": "Alice goda",
"members.$.address": "xyz",
"members.$.sex": "F"
}});
Still, be aware that the $ positional operator only updates the first array item that matches your query.
Unfortunately, there is no possibility of updating all the array elements that match your criteria in a single operation. As you can see on MongoDB Jira, the aforementioned feature is one of the most requested functionality, but it has not yet been directly implemented in MongoDB.

How do I create a conditional query that has optional $in with Mongoose?

I'm trying to return a list of documents that match a, or b, or c conditions.
Right now, I can only get it to work by matching ALL conditions, not just one...
I have tried this:
return User
.find()
.where({ $or: [
{ $in: skillTags },
{ $in: roleTags }
]})
But I get an error.
This one works but is not what I want as it only returns results that match both a skillTag and a roleTag. I want docs that match at least one or the other or both:
return User
.find()
.where({
skillTags: { $in: skillTags }
}, {
roleTags: { $in: roleTags }
})
This works to find docs that match either a skillTag or a roleTag:
return User
.find({
$or: [{
skillTags: {
$in: skillTags
}
}, {
roleTags: {
$in: roleTags
}
}]
})

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