How to retrieve Mongodb child value without parents object/field name - mongodb

Is there a way to query one type of child objects (child object/field with same name under different parents object/field ) directly without invoking the parents name in the find() command
For example I have
MondoDB
{
eatable.fruits.tomato
}
{
eatable.vegetables.tomato
}
Here each tomato is a parameter which have some value assigned in it, And I have tomato under two different objects/fields,
Is there a way to query and retrieve all values of tomato without using the field/object names "fruits" or "vegetables" in the find () command.

As of now, there is no direct way to search for child fields without knowing the parent fields but we do have the workaround. The idea is to first filter the documents by adding the constraints in find() method and then iterate over the response and print out the values of 'tomato' key where ever its present as leaf element.
Following is the example:
db.collection.find().forEach(
function processDoc(document){
let keys = Object.keys(document);
keys.forEach(
function(key){
let value = document[key];
if(typeof value === typeof Object()){
processDoc(value); // Its an embedded document, thus process it again
}else if(key == 'tomato'){
print(value);
}
}
);
}
);
Data set:
{
"_id" : ObjectId("5d63ac599d32d1c15cf5ea5e"),
"eatable" : {
"vegetables" : {
"tomato" : "veg1"
},
"fruits" : {
"tomato" : "fru1",
"banana": "fru1"
},
"misc" : {
"item" : {
"tomato" : "misc1"
}
}
}
}
{
"_id" : ObjectId("5d63ac599d32d1c15cf5ea5f"),
"eatable" : {
"fruits" : {
"tomato" : "fru2"
}
}
}
Output:
veg1
fru1
misc1
fru2

Related

Find records with field in a nested document when parent fields are not known

With a collection with documents like below, I need to find the documents where a particular field - eg. lev3_field2 (in document below) is present.
I tried the following, but this doesn't return any results, though the field lev3_field2 is present in some documents.
db.getCollection('some_collection').find({"lev3_field2": { $exists: true, $ne: null } })
{
"_id" : ObjectId("5884de15bebf420cf8bb2857"),
"lev1_field1" : "139521721",
"lev1_field2" : "276183",
"lev1_field3" : {
"lev2_field1" : "4",
"lev2_field2" : {
"lev3_field1" : "1",
"lev3_field2" : {
"lev4_field1" : "1",
"lev4_field2" : "1"
},
"lev3_field3" : "5"
},
"lev2_field3" : {
"lev3_field3" : "0",
"lev3_field4" : "0"
}
}
}
update1: this is an example, however in the real document it is not known what the parent fields are for the field to look for. So instead of lev3_field2 , I would be looking for `levM_fieldN'.
update2: Speed is not a primary concern for me, I can work with relatively a bit slower options as well, as the primary function is to find documents with the criteria discussed and once the document is found and the schema is understood, the query can be re-written for performance by including the parent keys.
To search a key in nested document you need to iterate the documents fields recursively, you can do this in JavaScript by the help of $where method in MongoDB
The below query will search if a key name exists in a documents and its subdocuments.
I have checked this with the example you have given, and it is working perfectly fine.
db.getCollection('test').find({ $where: function () {
var search_key = "lev3_field2";
function check_key(document) {
return Object.keys(document).some(function(key) {
if ( typeof(document[key]) == "object" ) {
if ( key == search_key ) {
return true;
} else {
return check_key(document[key]);
}
} else {
return ( key == search_key );
}
});
}
return check_key(this);
}}
);
There is no built-in function to iterate over document keys in MongoDB, but you can achieve this with MapReduce. The main advantage is that all the code is executed directly in the MongoDB database, and not in the js client, so there is no network overhead, hence it should be faster than client side js
here is the script :
var found;
// save a function in MongoDB to iterate over documents key and check for
// key name. Need to be done only once
db.system.js.save({
_id: 'findObjectByLabel',
value: function(obj, prop) {
Object.keys(obj).forEach(function(key) {
if (key === prop) {
found = true
}
if (!found && typeof obj[key] === 'object') {
findObjectByLabel(obj[key], prop)
}
})
}
})
// run the map reduce fonction
db.ex.mapReduce(
function() {
found = false;
var key = this._id
findObjectByLabel(this, 'lev3_field2')
value = found;
if (found) {
// if the document contains the key we are looking for,
// emit {_id: ..., value: true }
emit(key, value)
}
},
function(key, values) {
return values
}, {
'query': {},
'out': {inline:1}
}
)
this output ( run on 4 sample doc, with only one containing 'lev3_field2' )
{
"results" : [
{
"_id" : ObjectId("5884de15bebf420cf8bb2857"),
"value" : true
}
],
"timeMillis" : 18,
"counts" : {
"input" : 4,
"emit" : 1,
"reduce" : 0,
"output" : 1
},
"ok" : 1
}
to run the script, copy it to a file name "script.js" for example, and then run from your shell
mongo databaseName < script.js
It's because you're trying to see if a nested field exists. This is the query you want:
db.some_collection.find({"lev1_field3.lev2_field2.lev3_field2": { $exists: true, $ne: null } })

Update a Map value document value using Mongo

I have a document like myPortCollection
{
"_id" : ObjectId("55efce10f027b1ca77deffaa"),
"_class" : "myTest",
"deviceIp" : "10.115.75.77",
"ports" : {
"1/1/x1" : {
"portId" : "1/1/x1",
healthState":"Green"
I tried to update
db.myPortCollection.update({
{ deviceIp:"10.115.75.77"},
{ "chassis.ports.1/1/x10.rtId":"1/1/x10" },
{ $set: { "chassis.ports.1/1/x10.healthState" : "Red" }
})
But I am getting error that attribute names mentioned is wrong,.Please help in specifying the syntax properly for embedded map document update.
The "query" portion is wrong as you have split conditions into two documents. It should be this:
db.myPortCollection.update(
{
"deviceIp":"10.115.75.77",
"chassis.ports.1/1/x10.rtId":"1/1/x10"
},
{ "$set": { "chassis.ports.1/1/x10.healthState" : "Red" } }
)
And as long as the query then matches ( valid data not shown in your question ) then the specified field will be set or added.

MongoDB nested element query based on value of property

For below mongo document, I am trying to write a projection query. Given an input of userId value and ObectId of a target (ex: 54073d80e4b0cbf1ce225f02 in below document), how do I find the document which has these values?
All the examples, I searched expect the user to know the "property" names to query against. In this case, property names in "target" element are not known ahead of time. They can be my-target-1, my-target-2 etc. SO I want to search the document based on the value of the property (54073d80e4b0cbf1ce225f02). Any suggestions?
{
"_id" : ObjectId("540786bbe4b0e4752fe93321"),
"name" : "foo name",
"userId" : "123456",
"targets" : {
"my-target-1" : { //my-target-1 is dynamic property.
"executionOrder" : {
"1" : { //"1" is dynamic property.
"_id" : ObjectId("54073d80e4b0cbf1ce225f02"),// this is my search key
"type" : "TYPE 1",
"version" : "2"
},
"2" : {
"_id" : ObjectId("54073d80e4b0cbf1ce225f03"),
"type" : "TYPE 2",
"version" : "2"
}
}
},
"my-target-2" : {
"executionOrder" : {
"1" : {
"_id" : ObjectId("54073d80e4b0cbf1ce225f04"),
"type" : "TYPE 1",
"version" : "2"
},
"2" : {
"_id" : ObjectId("54073d80e4b0cbf1ce225f05"),
"type" : "TYPE 2",
"version" : "2"
}
}
}
},
One of the solutions would be to use the Text search feature of MongoDB to achieve this,
To allow for text search on the fields with string content, use the
wildcard specifier ($**) to index all fields that contain string
content.
The following example indexes any string value in the data of every
field of every document in collection and names the index TextIndex:
db.collection.ensureIndex(
{ "$**": "text" }
)
and query for any text in any of the fields, the below will return you all the documents whose any of the fields in the document or its sub documents have matched the query string.
db.collection.aggregate([{$match:{$text:{$search:"searchString"}}}])
Note: your search will only return results if you index on text fields with string content.
And remember,
The query matches on the complete stemmed words. For example, if a
document field contains the word blueberry, a search on the term blue
will not match the document. However, a search on either blueberry or
blueberries will match
You need to be wise in using this to fit in to your requirement.
So, in your above example, in the subdocuments, you could make search on fields that are unique and are textual.
"_id" : ObjectId("54073d80e4b0cbf1ce225f02") // don't query this field.
rather query for "type" : "TYPE 1".
See Also:
Create text index on sub-document field wildcard
If you want to find documents that contain a certain value you can use $where clause with a function. The query below creates a function that iterates through all properties and values looking for certain value without knowing the property.
{$where: function() {
var deepIterate = function (obj, value) {
for (var field in obj) {
if (obj[field] == value){
return true;
}
var found = false;
if ( typeof obj[field] === 'object') {
found = deepIterate(obj[field], value)
if (found) { return true; }
}
}
return false;
};
return deepIterate(this, "573c79aef4ef4b9a9523028f")
}}
Of course you can modify the query in order to match pairs of certain keys and values. Just like this:
{$where: function() {
var deepIterate = function (obj, tested) {
for (var field in obj) {
if (field == tested[0]){
if (obj[field] == tested[1]){
return true;
}
}
var found = false;
if ( typeof obj[field] === 'object') {
found = deepIterate(obj[field], tested)
if (found) { return true; }
}
}
return false;
};
return deepIterate(this, ["myKey", "myvalue"])
}}
Obviously you can modify the function to query for many matching pairs of keys and values inside a object. Sky is the limit.

Mongodb update subdocument inside array field of a collection

I have a mongodb collection like
{
"_id" : ObjectId("5375ef2153bb790b20d8a660"),
"association" : [
{
"count" : 3,
"name" : "hayatdediğin"
},
{
"count" : 2,
"name" : "sadecesenolsan"
},
{
"count" : 2,
"name" : "üslupnamustur"
}
],
"tag_count" : 4,
"tag_name" : "vazgeçilmezolan",
"variation" : [
{
"count" : 4,
"name" : "VazgeçilmezOlan"
}
]
}
Each collection consists of tag_name, tag_count, array field association and array field variation. For each name inside association, there exists a different document same as this document. I need to add new field "total_count" inside each association dictionary whose value equals the tag_count of the name by querying the database.
I tried this code but its not working
db.hashtag.find().forEach(function (doc) {
if (doc.association.length != 0 ) {
doc.association.forEach(function (assoc) {
db.hashtag.find({'tag_name': assoc.name}).forEach(function(tag){
assoc.total_count=tag.tag_count;
})
});
}
});
After modifying each doc you need to call save on the collection to commit the change.
Assuming you're doing this in the shell:
db.hashtag.find().forEach(function (doc) {
if (doc.association.length != 0 ) {
doc.association.forEach(function (assoc) {
db.hashtag.find({'tag_name': assoc.name}).forEach(function(tag){
assoc.total_count=tag.tag_count;
});
});
// Save the changed doc back to the collection
db.hashtag.save(doc);
}
});
To update doc in database you have to use db.hashtag.update, not db.hashtag.find. Find only retrieves document from db.
I changed the previous method of looping using forEach and then saved the doc at last and the code worked.
db.hashtag.find().forEach(function (doc) {
var array = doc.association;
if (array != undefined){
for(var i=0;i<array.length;i++)
{
var obj = db.hashtag.findOne({'name':array[i].name});
var count = obj.count;
doc.association[i].total_count = count;
db.hashtag.save(doc);
}
}
});

MongoDB returns all records even after specifying fields

> db.checklistDB.find({},{title:1, _id:0})
{ "title" : "Untitled Checklist" }
{ }
{ }
{ }
{ }
{ }
> db.checklistDB.find({},{title:1})
{ "title" : "Untitled Checklist", "_id" : "4FaJcAkzAY3Geyggm" }
{ "_id" : "3imNYy8SPcRDjLcqz" }
{ "_id" : "977fPtvEn7hiStqzp" }
{ "_id" : "QcAEMnr6R7qfaWFR8" }
{ "_id" : "eEsmKMdQGYKqnhTNB" }
{ "_id" : "cL6R8qxwWhvTr2kmy" }
Hi Guys,
As you can see from the above, I rand 2 commands:
db.checklistDB.find( {} , { title : 1 } ) and
*db.checklistDB.find( {} , { title : 1 , _id : 0} )
Both queries returns 6 records which is all the records that exists in the database. I would imagine that it will only return records that have "title" as a field. Is there something I'm doing wrong?
Second argument for find is projection. In your case it looks for title field inside document, and if it doesn’t exist returns none. If you want to filter documents you should use query like this:
db.checklistDB.find({title: {$exists: true}}, {title:1, _id:0})
EDIT
If you take your query:
db.checklistDB.find({}, {title:1, _id:0})
It translates to: retrieve all documents ({} as query argument) and for every document give me title if exist or default value (none) if it doesn’t ({title:1, _id:0} as projection argument). Projection argument is used only to transform not to filter documents.