MongoDB positional operator with nested arrays - mongodb

Sample collection structure:
{
"_id" : ObjectId("57cfd62001ca2dd672cfebb1"),
"name" : "Category",
"parent" : ObjectId("57cfd5d101ca2dd672cfebb0"),
"posts" : [
{
"name" : "Post",
"author" : ObjectId("57cfd09401ca2dd672cfebac"),
"content" : "Some content.",
"comments" : [
{
"author" : ObjectId("57cfd09401ca2dd672cfebab"),
"content" : "First comment",
"rating" : 2
},
{
"author" : ObjectId("57cfd09401ca2dd672cfebac"),
"content" : "Second comment",
"rating" : 5
}
]
}
]
}
I would like to select all comments whose author is ObjectId("57cfd09401ca2dd672cfebab").
This query is working,
db.categories.find({ 'posts.comments.author':ObjectId("57cfd09401ca2dd672cfebab") })
but I would like to return only first matching comment with positional operator. Something like this is not working. Does MongoDB support positional operator with nested arrays?
db.categories.find({ 'posts.comments.author': ObjectId("57cfd09401ca2dd672cfebab") },
{ 'posts.comments.$': 1 })

You are having more than two level of nesting ...find() may not work ,instead try aggregation:-
> db.categories.aggregate([{$unwind:"$posts"},{$unwind:"$posts.comments"},
{$match:{"posts.comments.author":ObjectId("57cfd09401ca2dd672cfebab")}},
{$project:{_id:0,"posts.comments":1}}]).pretty()
Output:
{
"posts" : {
"comments" : {
"author" : ObjectId("57cfd09401ca2dd672cfebab"),
"content" : "First comment",
"rating" : 2
}
}
}

Related

Mongoose Join List

I am new to mongoose and I have two schemas as below
contentschema
{ "_id" : autogenerated
"title": "string",
"description": "string",
}
viewedschema
{ "_id" : autogenerated
"contentid": "ref content",
"viewedby": "string",
}
All the users who have viewed the content will be stored in the viewedschema collection which has the contentid reference.
Note :As the number of viewed records will be huge, i dont want to have the viewed within the content as embedded document.
In Mongoose, Is there a way to get all the contents (array of content schema) viewed. [Similar to inner join in SQL].
Thanks in Advance.
We can use $lookup to merge the documents in two different collections of same database and it performs a left outer join on the collections.
Let us the below documents in content collection
{
"_id" : ObjectId("59ef51f106b0505f997f84c8"),
"title" : "myfavoritesong",
"description" : "A wonderful composition using string instruments"
}
{
"_id" : ObjectId("59ef52ad06b0505f997f84ca"),
"title" : "myfavoritestory",
"description" : "An interesting short story with a twisted ending"
}
Documents in viewed collection
{
"_id" : ObjectId("59ef523706b0505f997f84c9"),
"contentid" : ObjectId("59ef51f106b0505f997f84c8"),
"viewedby" : "user1"
}
{
"_id" : ObjectId("59ef52f406b0505f997f84cb"),
"contentid" : ObjectId("59ef52ad06b0505f997f84ca"),
"viewedby" : "user2"
}
{
"_id" : ObjectId("59ef53c706b0505f997f84cc"),
"contentid" : ObjectId("59ef52ad06b0505f997f84ca"),
"viewedby" : "user3"
}
Final aggregate query using $lookup by combining the two collections is
db.viewed.aggregate({
$lookup:{
from : "content",
localField: "contentid",
foreignField:"_id",
as:"viewed_contents"
}
})
Result of the aggregate query for our sample data is
{
"_id" : ObjectId("59ef523706b0505f997f84c9"),
"contentid" : ObjectId("59ef51f106b0505f997f84c8"),
"viewedby" : "user1",
"viewed_contents" : [
{
"_id" : ObjectId("59ef51f106b0505f997f84c8"),
"title" : "myfavoritesong",
"description" : "A wonderful composition using string in
struments"
}
]
}
{
"_id" : ObjectId("59ef52f406b0505f997f84cb"),
"contentid" : ObjectId("59ef52ad06b0505f997f84ca"),
"viewedby" : "user2",
"viewed_contents" : [
{
"_id" : ObjectId("59ef52ad06b0505f997f84ca"),
"title" : "myfavoritestory",
"description" : "An interesting short story with a twist
ed ending"
}
]
}
{
"_id" : ObjectId("59ef53c706b0505f997f84cc"),
"contentid" : ObjectId("59ef52ad06b0505f997f84ca"),
"viewedby" : "user3",
"viewed_contents" : [
{
"_id" : ObjectId("59ef52ad06b0505f997f84ca"),
"title" : "myfavoritestory",
"description" : "An interesting short story with a twist
ed ending"
}
]
}
Please note you can also swap the collections from viewed as foreign and content as local
db.content.aggregate({
$lookup:{
from : "viewed",
localField: "_id",
foreignField:"contentid",
as:"contents_viewed_by"
}
})
Result of this aggregate query is as follows
{
"_id" : ObjectId("59ef51f106b0505f997f84c8"),
"title" : "myfavoritesong",
"description" : "A wonderful composition using string instruments",
"contents_viewed_by" : [
{
"_id" : ObjectId("59ef523706b0505f997f84c9"),
"contentid" : ObjectId("59ef51f106b0505f997f84c8"),
"viewedby" : "user1"
}
]
}
{
"_id" : ObjectId("59ef52ad06b0505f997f84ca"),
"title" : "myfavoritestory",
"description" : "An interesting short story with a twisted ending",
"contents_viewed_by" : [
{
"_id" : ObjectId("59ef52f406b0505f997f84cb"),
"contentid" : ObjectId("59ef52ad06b0505f997f84ca"),
"viewedby" : "user2"
},
{
"_id" : ObjectId("59ef53c706b0505f997f84cc"),
"contentid" : ObjectId("59ef52ad06b0505f997f84ca"),
"viewedby" : "user3"
}
]
}

Is it possible to return the documents of two collections?

I want to store the employees appeals in different collections based on their type. But am afraid that I want be able to return the content of those different collections at once, as I used to return the content of a collection as shown in the code below:
pendingAppeals = function() {
return Al.find({status: "Pending"});
}
So my concern is, if I have another collection called Ml, will I be able to return the content of both Al and Ml in the same time?
I am not sure which version of mongo you are using and meteor has support for it or not. But mongo 3.2 has a aggregate type join which fetches data from multiple collections depending on your condition. Refer: https://docs.mongodb.com/v3.2/reference/operator/aggregation/lookup/
Not really too sure about your data structure, if you could provide an example it would be easier to work with, however take the following example where we have a students and a teachers collection:
> db.students.find()
{ "_id" : ObjectId("584020b6410ebb5a4ea03393"), "name" : "bob" }
{ "_id" : ObjectId("584020b6410ebb5a4ea03394"), "name" : "foo" }
{ "_id" : ObjectId("584020b7410ebb5a4ea03395"), "name" : "bill" }
> db.teachers.find().pretty()
{
"_id" : ObjectId("584020e7410ebb5a4ea03396"),
"name" : "t 1",
"studentIds" : [
ObjectId("584020b6410ebb5a4ea03393"),
ObjectId("584020b7410ebb5a4ea03395")
]
}
{
"_id" : ObjectId("584020ff410ebb5a4ea03397"),
"name" : "t 1",
"studentIds" : [
ObjectId("584020b6410ebb5a4ea03394"),
ObjectId("584020b7410ebb5a4ea03395")
]
}
Then we can use the aggregation framework with $unwind and $lookup stages:
db.teachers.aggregate([
{$unwind: "$studentIds"},
{$lookup: {
from: "students",
localField: "studentIds",
foreignField: "_id",
as: "students"
}
}
])
and we'll get the following output:
{
"_id" : ObjectId("584020e7410ebb5a4ea03396"),
"name" : "t 1",
"studentIds" : ObjectId("584020b6410ebb5a4ea03393"),
"students" : [
{
"_id" : ObjectId("584020b6410ebb5a4ea03393"),
"name" : "bob"
}
]
}
{
"_id" : ObjectId("584020e7410ebb5a4ea03396"),
"name" : "t 1",
"studentIds" : ObjectId("584020b7410ebb5a4ea03395"),
"students" : [
{
"_id" : ObjectId("584020b7410ebb5a4ea03395"),
"name" : "bill"
}
]
}
{
"_id" : ObjectId("584020ff410ebb5a4ea03397"),
"name" : "t 1",
"studentIds" : ObjectId("584020b6410ebb5a4ea03394"),
"students" : [
{
"_id" : ObjectId("584020b6410ebb5a4ea03394"),
"name" : "foo"
}
]
}
{
"_id" : ObjectId("584020ff410ebb5a4ea03397"),
"name" : "t 1",
"studentIds" : ObjectId("584020b7410ebb5a4ea03395"),
"students" : [
{
"_id" : ObjectId("584020b7410ebb5a4ea03395"),
"name" : "bill"
}
]
}
Refs:
https://docs.mongodb.com/v3.2/reference/operator/aggregation/
https://docs.mongodb.com/v3.2/reference/operator/aggregation/lookup/

How to query for and delete embedded objects in an array field in MongoDB?

I am trying to run an update command that finds an object in an array field for all documents and then pulls it from that array field for all documents. I have tried the below commands, which do not return any documents (nMatched: 0).
db.users.update({"likedQuizzes.quiz": { $elemMatch: quizObj}},{$pull: {"likedQuizzes.quiz": quizObj }});
db.users.update({"likedQuizzes": { $elemMatch: quizObj}},{$pull: {"likedQuizzes": quizObj }});
In the above cases quizObj = {"title" : "2 Purple", "author" : "purple-tester1"}
Here is a sample document from the 'users' collection:
{
"_id" : ObjectId("581114330de9ac0c1445cdd6"),
"user" : "test-username1",
"likedQuizzes" : [
{
"quiz" : {
"title" : "2 Purple",
"author" : "purple-tester1"
},
"date" : ISODate("2016-10-26T20:39:02.695Z")
},
{
"quiz" : {
"title" : "1 Purple",
"author" : "purple-tester1"
},
"date" : ISODate("2016-10-26T20:39:12.374Z")
},
{
"quiz" : {
"title" : "4 Green",
"author" : "green-tester1"
},
"date" : ISODate("2016-10-26T20:39:25.304Z")
},
{
"quiz" : {
"title" : "3 Green",
"author" : "green-tester1"
},
"date" : ISODate("2016-10-26T20:39:37.326Z")
},
{
"quiz" : {
"title" : "2 Green",
"author" : "green-tester1"
},
"date" : ISODate("2016-10-26T20:40:12.964Z")
}
]
}
Said another way, I am simply trying to find if a quiz is in a users' "likedQuizzes" array, and if it is, delete it from that array (along with the "date" that it was liked).
After some researching it looks like this might not be possible, but I wanted to ask anyway because I think my schema might be different enough from the examples that came up in my research. Thanks in advance for any pointers.
You could restructure your document to be of this structure:
{
"_id" : ObjectId("581114330de9ac0c1445cdd6"),
"user" : "test-username1",
"likedQuizzes" : [
{
"title" : "2 Purple",
"author" : "purple-tester1",
"date" : ISODate("2016-10-26T20:39:02.695Z")
},
{
"title" : "1 Purple",
"author" : "purple-tester1",
"date" : ISODate("2016-10-26T20:39:12.374Z")
},
{
"title" : "4 Green",
"author" : "green-tester1",
"date" : ISODate("2016-10-26T20:39:25.304Z")
},
{
"title" : "3 Green",
"author" : "green-tester1",
"date" : ISODate("2016-10-26T20:39:37.326Z")
},
{
"title" : "2 Green",
"author" : "green-tester1",
"date" : ISODate("2016-10-26T20:40:12.964Z")
}
]
}
After that... if you run the following command:
db.users.update(
{
"likedQuizzes": {
$elemMatch: {
"title" : "2 Purple",
"author" : "purple-tester1"
}
}
},
{
$pull: {
"likedQuizzes": {
"title": "2 Purple",
"author": "purple-tester1"
}
}
}
);
Your $pull will now work. $pull doesn't seem to play nicely with $elemMatch
Well, what you are trying to do is to pass the whole quiz object to the $elemMatch and expecting it to return the match documents from MongoDB, but it won't work that way.
You have to modify your query a bit like
{"likedQuizzes.quiz.title": { $elemMatch: quizObj.title}
Reason : mongodb won't do the field by field compare for you. You should have a unique identifier based on which you can query.
Even in the RDBMS world you cannot do this.
What you are trying to do is something like:
quizObj q;
q.title = "2 Purple";
q.author = "purple-tester1";
select * from quizTable where row = q.
You can give it a try
db.users.update({"likedQuizzes": { $elemMatch: {"quiz.title" : "2 Purple", "quiz.author" : "purple-tester1"}}},
{$pull: {"likedQuizzes": {"quiz" : {"title" : "2 Purple", "author" : "purple-tester1"}}}})
What you can do is use your quizObj = {"quiz.title" : "2 Purple", "quiz.author" : "purple-tester1"}
Hope it helps.
And then below query
db.users.update({"likedQuizzes": { $elemMatch: quizObj }},{$pull: {"likedQuizzes": quizObj }})

Conditional $inc in MongoDB query

I have a survey system with documents like this:
{
"_id" : ObjectId("555b0b33ed26911e080102c4"),
"question" : "survey",
"subtitle" : "",
"answers" : [
{
"title" : "option 1",
"color" : "#FFEC00",
"code" : "opt1",
"_id" : ObjectId("555b0b33ed26911e080102ce"),
"votes" : 0,
"visible" : true
},
{
"title" : "option 2",
"color" : "#0bb2ff",
"code" : "opt2",
"_id" : ObjectId("555b0b33ed26911e080102cd"),
"votes" : 0,
"visible" : true
}
]
}
Now, I'm working on submit vote, so I need to increase 'votes' field for an specific survey (depending on option selected by user).
My problem is: I can have multiple documents like that, so how can I $inc field votes inside this array for an specific document? I tried this query (based on this website), but it didn't work:
db.bigsurveys.update(
{_id: ObjectId('555b0b33ed26911e080102c4'), 'answers.$.code' : 'opt1'},
{ $inc : { 'answers.$.votes' : 1 } }
)
The main problem here is that I can have multiple documents like this. Thanks in advance!
Use $elemMatch and postional operator $ to update query as :
db.bigsurveys.update({
"_id": ObjectId("555b0b33ed26911e080102c4"),
"answers": {
"$elemMatch": {
"code": "opt1"
}
}
}, {
"$inc": {
"answers.$.votes": 1
}
})

need to find a way to get data inside subdocument in mongo

I need a way to find inside nested array documents.
I want to find value matching inside street_1.
this is my query:
db.phonebook.find({'address.home.street_1' : 'street 1 result'});
and my document:
{
"_id" : ObjectId("53788c0c74d3ead0098b4568"),
"first_name" : "jarod",
"last_name" : "petters",
"company" : "nostromos",
"phone_numbers" : [
{
"cell" : "0752203337"
},
{
"home" : "0850819201"
},
{
"home" : "0499955550"
}
],
"website" : "http://www.mywebsite.com",
"email" : [
{
"home" : "email.first.com"
},
{
"office" : "email.second.com"
}
],
"address" : [
{
"home" : {
"stree_1" : "street 1 result",
"stree_2" : "",
"postal_code" : "66502",
"city" : "my littre city",
"country" : "usa"
}
}
],
"nationality" : "mars",
"date_of_birth" : "1978-01-01"
}
Your query is the good one. But your document is not good, you have done a mistake in your document, you write stree_1 instead of street_1. If your document is right, change your query to :
db.phonebook.find({'address.home.stree_1' : 'street 1 result'});