How do I implement Mongo DB query to find key in all objects inside an array? - mongodb

After looking into several books, the mongo db reference and other stackoverflow questions, I can't seem to get this query right.
I have the following data structure:
Posts collection:
{
"content" : "...",
"comments" : [
{
"author" : "joe",
"score" : 3,
"comment" : "nice post"
},
{
"author" : "bob",
"score" : 5,
"comment" : "nice"
}]
}
What I'm trying to get is all of the author names inside each of the objects of the array inside a Handlebars helper, not in the console. So I would have something like:
...
commentsAuthors: function() {
return Collection.find({...});
}
UPDATE:
I decided to reduce my array to only an Array of strings which I later queried like this:
New Array:
{
"content" : "...",
"comments" : ["value1", "value2", "..."]
}
MeteorJS Handlebars helper:
Template.courseEdit.helpers({
comments: function(){
var cursor = Courses.find({"_id": "8mBATtGyyWsGr2PwW"}, {"comments": 1, "_id": 0}).fetch();
return cursor.map(function(doc, index, cursor){
var comment = doc.comments;
console.log(comment);
return comment;
});
}
});
At this point, I'm able to render the Whole array in my view with this {{#each}}...{{/each}}:
<ul class="list-unstyled">
{{#each comments}}
<li class="pl-14"><i class="icon-checkmark"></i> {{this}}</li>
{{/each}}
</ul>
However I'm getting the whole array in one single list item.
How can I create a separate list item for each of my Array strings?
Thanks in advance.

Should be as easy as (replace posts with your collection name):
db.posts.distinct("comments.author")
Or if you need it per post:
db.posts.aggregate([
{ $unwind: "$comments" },
{ $group: { "_id": "$_id", authors: { "$addToSet": "$comments.author"} } }
])

You can get only certain fields by limiting fields
db.collection.find({"content" : "..."},{"comments.author":1})

Related

Finding a nested document and pushing a document into a nested document

So i started learning mongoDB and moongoose today. I have the following Schema:
{
username: {
type : String,
required : true,
unique: true,
trim : true
},
routines : {
type: [
{
_id : mongoose.Schema.Types.ObjectId,
name : String,
todos : [
{
_id : mongoose.Schema.Types.ObjectId,
todo : String,
isCompleted : Boolean
}
]
}
],
}
}
for example:
{
"username": "John",
"routines": [
{
"_id" : 1234, //just for an example assume it to be as 1234
"name" : "Trip plan",
"todos" : [
{
"_id": 1213123,
"todo": "book flight",
"isCompleted" : "false"
}....
]
}......
]
}
what i want to do is, first i would select a document from collection, by using username, then in routines(Array of objects), i want to select a particular routine by id, which would be given by user in request body, now in the selected routine, i want to push into todos array.
For above example, suppose, selecting document with username john, then selecting an object from routines array with _id as 1234, then to its todos i will add a todo.
I have spent almost a day searching about how to do this, while doing this i learnt concepts like arrayFilters, projections. But still couldn't get how to do. Also i read many answers in Stack Overflow, i couldn't grasp much out of them.
PS:I am very new to MongoDB and mongoose, Chances that my question is very silly, and might not be good as per stack overflow standards, I apologize for the same.
You were on the right track, you indeed want to use arrayFilter to achieve this.
Here is a quick example:
// the user you want.
let user = user;
await db.routines.updateOne(
{
username: user.username
},
{
$addToSet: {
"routines.$[elem].todos": newTodo
}
},
{arrayFilters: [{'elem._id': "1234"}]}
);

The dollar ($) prefixed field '$oid' in 'features.0._id.$oid' is not valid for storage

I have the following Mongo collection attached below.
it is existing one not new collection created, I am trying to push new item in the features array, but I am not able to achieve that due to the following error: The dollar ($) prefixed field '$oid' in 'features.0._id.$oid' is not valid for storage.
using this script:
db.getCollection("test").update({ _id: "RYB0001" },
{
$push: {
"features": {
"_id": {
"$oid": ObjectId(),
},
"featureItems": [],
"typeId": "type3"
}
}
});
If it is a driver version issue, any workaround can I do to push the new item to the current collection?
MongoDb version: 4.0.4
Mongo Collection screenshot
The $oid notation is part of MongoDB's Extended JSON. I assume the data in your database doesn't actually have that key-value pair and it was only represented that way after using something like JSON.stringify(obj)
> db.test.find({});
{ "_id" : "RYB0001", "features" : [ { "_id" : ObjectId("5e40d46a97abdef3faa0d5d9"), "featureItems" : [ ], "typeId" : "type3" } ] }
> JSON.stringify(db.test.find({})[0]);
{"_id":"RYB0001","features":[{"_id":{"$oid":"5e40d46a97abdef3faa0d5d9"},"featureItems":[],"typeId":"type3"}]}
You'll need to generate a new ObjectId using its constructor "_id": new ObjectId(), note the new keyword there, I think #Thilo may have missed that in their comment.
db.getCollection("test").update({ _id: "RYB0001" },
{
$push: {
"features": {
"_id": new ObjectId(),
"featureItems": [],
"typeId": "type3"
}
}
});

How to get all matching subdocuments in mongoose?

In my mongodb (using Mongoose), I have story collection which has comments sub collection and I want to query the subdocument by client id, as
Story.find({ 'comments.client': id }, { title: 1, 'comments.$': 1 }, function (err, stories) {
...
})
})
The query works except that it only returns the first matched subdocument, but I want it to return all matching subdocuments. Did I miss an option?
EDIT:
On Blakes Seven's tip, I tried the answers from Retrieve only the queried element in an object array in MongoDB collection, but I couldn't make it work.
First tried this:
Story.find({'comments.client': id}, { title: 1, comments: {$elemMatch: { client: id } } }, function (err, stories) {
})
It also returns the first match only.
Then, I tried the accepted answer there:
Story.aggregate({$match: {'comments.client': id} }, {$unwind: '$comments'}, {$match : {'comments.client': id} }, function (err, stories) {
})
but this returns nothing. What is wrong here?
UPDATE:
My data structure looks like this:
{
"_id" : ObjectId("55e2185288fee5a433ceabf5"),
"title" : "test",
"comments" : [
{
"_id" : ObjectId("55e2184e88fee5a433ceaaf5"),
"client" : ObjectId("55e218446033de4e7db3f2a4"),
"time" : ISODate("2015-08-29T20:16:00.000Z")
}
]
}

How can I write a Mongoose find query that uses another field as it's conditional?

Consider the following:
I have a Mongoose model called 'Person'. In the schema for the Person mode, each Person has two fields: 'children' and 'maximum_children'. Both fields are of type Number.
I would like to write a find query that returns Persons when that Persons 'children' value is less that it's 'maximum_children' value.
I have tried:
person_model.find({
children: {
$lt: maximum_children
}
}, function (error, persons) {
// DO SOMETHING ELSE
});
and
person_model.find({
children: {
$lt: 'maximum_children'
}
}, function (error, persons) {
// DO SOMETHING ELSE
});
I'm doing something wrong in trying to specify the field name that I want to compare 'children' against.
OK.
I found a solution, just after I posted this question.
The answer seems to be:
person_model.find({
$where: "children < maximum_children"}, function (error, persons)
}, {
// DO SOMETHING ELSE
});
Seems to work OK, although it seems messy.
$where must execute its JavaScript conditional against every doc so its performance can be quite poor. Instead, you can use aggregate to include a new field in a $project stage the indicates whether the doc matches or not and then filter on that:
person_model.aggregate([
{$project: {
isMatch: {$lt: ['$children', '$maximum_children']},
doc: '$$ROOT'
}},
{$match: {isMatch: true}},
{$project: {_id: 0, doc: 1}}
], function(err, results) {...});
This uses $$ROOT to include the original doc as the doc field of the projection, with a final $project used to remove the isMatch field that was added.
results looks like:
{
"doc" : {
"_id" : ObjectId("54d04591257efd80c6965ada"),
"children" : 5,
"maximum_children" : 10
}
},
{
"doc" : {
"_id" : ObjectId("54d04591257efd80c6965add"),
"children" : 5,
"maximum_children" : 6
}
}
If you want to remove the added doc level of the objects you can use Array#map on results like so:
results = results.map(function(item) { return item.doc; });
Which reshapes results to put them back into their original form:
{
"_id" : ObjectId("54d04591257efd80c6965ada"),
"children" : 5,
"maximum_children" : 10
},
{
"_id" : ObjectId("54d04591257efd80c6965add"),
"children" : 5,
"maximum_children" : 6
}

Mongodb: Trying to find all documents with specific subdocument field, why is my query not working?

Here is an example of a document from the collection I am querying
meteor:PRIMARY> db.research.findOne({_id: 'Z2zzA7dx6unkzKiSn'})
{
"_id" : "Z2zzA7dx6unkzKiSn",
"_userId" : "NtE3ANq2b2PbWSEqu",
"collaborators" : [
{
"userId" : "aTPzFad8DdFXxRrX4"
}
],
"name" : "new one",
"pending" : {
"collaborators" : [ ]
}
}
I want to find all documents within this collection with either _userId: 'aTPzFad8DdFXxRrX4' or from the collaborators array, userId: 'aTPzFad8DdFXxRrX4'
So I want to look though the collection and check if the _userId field is 'aTPzFad8DdFXxRrX4'. If not then check the collaborators array on the document and check if there is an object with userId: 'aTPzFad8DdFXxRrX4'.
Here is the query I am trying to use:
db.research.find({$or: [{_userId: 'aTPzFad8DdFXxRrX4'}, {collaborators: {$in: [{userId: 'aTPzFad8DdFXxRrX4'}]}}] })
It does not find the document and gives me a syntax error. What is my issue here? Thanks
The $in operator is basically a simplified version of $or but you really only have one argument here so you should not even need it. Use dot notation instead:
db.research.find({
'$or': [
{ '_userId': 'aTPzFad8DdFXxRrX4'},
{ 'collaborators.userId': 'aTPzFad8DdFXxRrX4'}
]
})
If you need more than one value then use $in:
db.research.find({
'$or': [
{ '_userId': 'aTPzFad8DdFXxRrX4'},
{ 'collaborators.userId': {
'$in': ['aTPzFad8DdFXxRrX4','aTPzFad8DdFXxRrX5']
}}
]
})