MongoDB nested element query based on value of property - mongodb

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.

Related

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

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

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 } })

Using Mongodb _id field to query partially on one or more of the composite fields

I am using _id field as a compound key for my document with 2 fields as below.
{
"_id" : {
"timestamp" : ISODate("2016-08-25T05:43:00.000-19:30"),
"hostName" : "nj"
}
}
What I noticed it I am able to only query if I use both the fields together in my query. If I use one of them, I do not get any documents returned.
db.getCollection('sales').find(
{
"_id" : {
"hostName" : "tryme"
}
}
db.getCollection('sales').find(
{
"_id" : {
"timestamp" : ISODate("2016-08-25T05:43:00.000-19:30")
}
}
The above script does not return any documents.
Also, I am not able to use $gte/$lte operators on the date fields,
db.getCollection('sales').find(
{
"_id" : {
"timestamp" : {
"$lte":ISODate("2016-08-25T04:51:00.000-19:30")
},
"hostName" : "tryme"
}
}
)
The above also does not return any docs.
The below queries works but I see as per explain() it uses a collection scan and index is not used.
db.getCollection('sales').find(
{
"_id.timestamp" : ISODate("2016-08-25T04:51:00.000-19:30"),
"_id.hostName" : "tryme"
}
)
==
db.getCollection('sales').find(
{
"_id.timestamp" : {
"$gte": ISODate("2016-08-25T04:52:00.000-19:30")
},
"_id.hostName" : "tryme"
}
)
Not sure If I have understood how the _id field works correctly.
Basically, I want to be able to use partial fields of the composite query and also use the date type field also for range queries like between/greaterthan/lesser than etc at the same time leveraging the index on the _id field.
Can someone please help me on this.
Thanks,
Sri
From the docs:
MongoDB uses the dot notation to access the elements of an array and
to access the fields of an embedded document.
Your firsts attempts doesn't work because you are passing a nested object as query which matches for equality, use dot notation instead.

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 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.