How to write Mongo query to find sub document with condition - mongodb

I have a document in a collection like this, I need to find the record with form_Id:1 and Function_Id:2, how to write the mongo query.
"Form_Id" : 1,
"Function" : [{
"Function_Id" : 1,
"Role" : [{
"Role_Id" : 1,
"UserId" : ["Admin", "001"]
}]
}, {
"Function_Id" : 2,
"Role" : [{
"Role_Id" : 2,
"UserId" : ["Admin", "005"]
}]
}]

You can use dot notation and the $ positional projection operator to do this:
db.test.find({Form_Id: 1, 'Function.Function_Id': 2}, {_id: 0, 'Function.$': 1})
returns:
{"Function": [{"Function_Id": 2, "Role": [{"Role_Id": 2, "UserId": ["Admin", "005"]}]}]}

Since your function key is an array, in order to use the $match operator, first you have to use the $unwind operator. http://docs.mongodb.org/manual/reference/aggregation/unwind/
And then you use $match operator to find the documents that you want http://docs.mongodb.org/manual/reference/aggregation/match/
So your query should look like this
db.collection.aggregate([{$unwind:"$Function"},{$match:{"Form_id":1,"Function.Function_id":2}}])
By default mongo will display the _id of the document. So if you do not want to display the _id, after matching the relevant ones, you could use the $project operator http://docs.mongodb.org/manual/reference/aggregation/project/
db.collection.aggregate([{$unwind:"$Function"},{$match:{"Form_id":1,"Function.Function_id":2}},{$project:{"_id":0,"Form_id":1,"Function":1}}])
If you don't want the form_id to be displayed, simply don't specify the form_id in the project part of the query. By default mongo will only display the keys whose value is 1. If the key is not mentioned it will not display it.
db.collection.aggregate([{$unwind:"$Function"},{$match:{"Form_id":1,"Function.Function_id":2}},{$project:{"_id":0,"Function":1}}])

Related

Use distinct() on a variable in mongodb

I'm trying several queries in mongodb. Each document of my colelction is like this :
{
"_id" : 1,
"name" : 1,
"isReferenceProteome" : 1,
"isRepresentativeProteome" : 1,
"component" : 1,
"reference" : 1,
"upid" : 1,
"modified" : 1,
"taxonomy" : 1,
"superregnum" : 1,
"description" : 1,
"dbReference" : 1
}
the "reference" field has nested fields, one is "authorList", an array containing 'name' fields.
"reference" {
"authorList" [
{"name": "author1"},
{"name": "author2"},
{"name": "author3"} ...etc...
]
}
I have stored in a variable the result of the following query :
var testing = db.mycollection.find({'reference.authorList.30': {$exists: true}})
which stores all documents where the authorList is at least 30 names long.
Then I wanted to use distinct() on this variable, in order to have the distinct names of all authors :
testing.distinct("reference.authorList.name")
I tried this way because my first query returned an empty array :
db.mycollection.distinct( "reference.authorList.name", {"reference.authorList.name.30": {$exists: true}} )
I'm also trying whit $where command, but I got syntaxError for now.
What I am missing ?
Thanks.
Use
db.head_human_prot.distinct( "reference.authorList.name", {"reference.authorList.30": {$exists: true}} )
instead of
db.head_human_prot.distinct( "reference.authorList.name", {"reference.authorList.name.30": {$exists: true}} )
Silly me...

incrementing version number in a subdocument and the parent document

I am trying to add versioning levels in a document as well as its subdocuments. Heres a schema example
{
"_id" : ObjectId("59d1312a8ee6de1858933950"),
"synonyms" : [
{
"_id" : ObjectId("59d1312a8ee6de1858933954"),
"text" : [
1.0,
2.0,
3.0
],
"__v" : 1.0
},
{
"_id" : ObjectId("59d1312a8ee6de1858933953"),
"text" : [
"Foo ",
"bar ",
"Baz"
],
"__v" : 0
},
{
"_id" : ObjectId("59d1312a8ee6de1858933951"),
"text" : [
"fizz",
"bazz",
"bizz"
],
"__v" : 0
}
],
"__v" : 3.0
}
As you can see , the parent document has its own __v while each subdocument (part of the synonyms array) also has its __v . What i'm trying to accomplish is this
When updating a subdocument array - increment the version of the subdocument as well as its parent version
to that effect , i've tried the below code
db.collection.update({
'_id': ObjectId("59d1312a8ee6de1858933950"),
"synonyms._id": ObjectId("59d1312a8ee6de1858933954")
},
{$set: {'synonyms.$.text': [1,2,3]}, $inc: {'synonyms.$.__v': 1}, $inc: {"__v": 1}}
)
My parent __v is getting increment on every update but the subdocument seems to be stuck at 1.0 no matter how many updates i go through. Is there a better way?
If you think about the parameters that you pass to a MongoDB command as a JSON document rather than a string this makes perfect sense: Passing in the same operator twice (as in $inc at the start and later another $inc again in your example) will create a JSON document that only contains the last parameter. This would be different if you were using a string here which would actually represent a JSON document with two $inc fields.
So here's how to get it right (basically by $incing two fields as part of a single operation):
db.collection.update({
'_id': ObjectId("59d1312a8ee6de1858933950"),
"synonyms._id": ObjectId("59d1312a8ee6de1858933954")
},
{$set: {'synonyms.$.text': [1,2,3]}, $inc: {'synonyms.$.__v': 1, "__v": 1}}
)

Multiple update in a document in MongoDB

I am trying to update multiple nested documents in a document in mongoDB.
Say my data is:
{
"_id" : "ObjectId(7df78ad8902c)",
"title" : "Test",
"img_url" : "[{s: 1, v:1}, {s: 2, v: 2}, {s: 3, v: 3}]",
"tags" : "['mongodb', 'database', 'NoSQL']",
"likes" : "100"
}
I want to update v to 200 for s = 1 and s= 2 in img_url list.
It is easy to update v for any single s.
Is there any way to update multiple documents satisfying some criteria.
I tried:
db.test.update({ "_id" : ObjectId("7df78ad8902c"), "img_url.s": {$in : ["1", "2"]}}, {$set: { "img_url.$.v" : 200 } });
and
db.test.update({ "_id" : ObjectId("7df78ad8902c"), "img_url.s": {$in : ["1", "2"]}}, {$set: { "img_url.$.v" : 200 } }, {mulit: true});
Some sources are suggesting it is not possible to do so.
Multiple update of embedded documents' properties
https://jira.mongodb.org/browse/SERVER-1243
Am I missing something ?
For the specific case/example you have here. You are specifying an _id which means you are to update only one with that specific _id.
to update img_url try without the _id; something like this:
db.test.update({}, {"$set":{"img_url.0":{s:1, v:400}}}, {multi:true})
db.test.update({}, {"$set":{"img_url.1":{s:2, v:400}}}, {multi:true})
0 and 1 in img_url are the array indexes for s:1 and s:2
in order to update based on specific criteria you need to set the attribute you need on the first argument. say for example, to update all documents that have likes greater than 100 increment by 1 you do (assuming likes type is int...):
db.people.update( { likes: {$gt:100} }, {$inc :{likes: 1}}, {multi: true} )
hope that helps

MongoDB Why this error : can't append to array using string field name: comments

I have a DB structure like below:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"content" : "xxx"
}
]
}
I update a new subdocument in the comments feild. It is OK.
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
after that the DB structure:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"comments" : [
{
"id" : 3,
"content" : "xxx"
}
],
"content" : "xxx"
}
]
}
But when I update a new subdocument in the comment field that _id is 3, There is a error:
db.test.update(
{"_id" : 1, "comments.comments.id" : 3},
{$push : {"comments.comments.$.comments" : {id : 4, content:"xxx"}}}
)
error message:
can't append to array using string field name: comments
Well, it makes total sense if you think about it. MongoDb has the advantage and the disadvantage of solving magically certain things.
When you query the database for a specific regular field like this:
{ field : "value" }
The query {field:"value"} makes total sense, it wouldn't in case value is part of an array but Mongo solves it for you, so in case the structure is:
{ field : ["value", "anothervalue"] }
Mongo iterates through all of them and matches "value" into the field and you don't have to think about it. It works perfectly.. at only one level, because it's impossible to guess what you want to do if you have multiple levels
In your case the first query works because it's the case in this example:
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
Matches _id in the first level, and comments._id at the second level, it gets an array as a result but Mongo is able to solve it.
But in the second case, think what you need, let's isolate the where clause:
{"_id" : 1, "comments.comments.id" : 3},
"Give me from the main collection records with _id:1" (one doc)
"And comments which comments inside have and id=3" (array * array)
The first level is solved easily, comments.id, the second is not possible due comments returns an array, but one more level is an array of arrays and Mongo gets an array of arrays as a result and it's not possible to push a document into all the records of the array.
The solution is to narrow your where clause to obtain an unique document in comments (could be the first one) but it's not a good solution because you never know what is the position of the document you're looking for, using the shell I think the only option to be accurate is to do it in two steps. Check this query that works (not the solution anyway) but "solves" the multiple array part fixing it to the first record:
db.test.update(
{"_id" : 1, "comments.0.comments._id" : 3},
{$push : {"comments.0.comments.$.comments" : {id : 4, content:"xxx"}}}
)

MongoDB aggregation group

I need few values to pass through the pipeline(without changes) to the subsequent pipeline aggregator while using a group
For eg if my input is
{ "_id" : 1, "tags" : [ "a", "b", "b", "c" ], "text" : "a" },
{ "_id" : 2, "tags" : [ "a", "c" ], "text" : "b" }
and I like the output to be
{ _id: 1, tags : [a,b,c], text : a} , { _id: 2, tags : [a,c], text : b}
I did an unwind and group and do $addToSet to remove the dups. My question is how do I make
text appear in the output as is. the text key and values need to just pass through this pipeline, However i am forced to use accumulators
I tried this code so far
use test
var unwind = { $unwind : "$tags"};
var group = { $group : { _id : "$_id" , tags : { $addToSet : "$tags" }
, text : { $first : "$text" }}};
db.test.aggregate(unwind,group)
and it works fine but that is not the intention of $first and it is suggested to be used with sort. What is the right way to do this ?
There's nothing wrong with using $first for this. As an alternative, you could add text to your _id, but that's hardly better as then you'd need to add a $project to the end of your pipeline to move it back out of _id.
You have to do the same sort of mildly awkward thing with SQL group bys.
All fields but the _id field must use an accumulator with $group, as specified in the $group documentation page. The problem is that it's unclear what it would mean to "pass" a value through the pipeline with $group, since MongoDB has no way of knowing that the value of that field is going to be the same for all documents you are grouping together. You have to choose what value you want for that field with the accumulator.