Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB Query Help - query on values of any key in a sub-object

Tags:

mongodb

People also ask

How do I query a nested object in MongoDB?

Query on Nested Field To specify a query condition on fields in an embedded/nested document, use dot notation ( "field. nestedField" ). When querying using dot notation, the field and nested field must be inside quotation marks.

How do I query a key in MongoDB?

There are two ways to do an OR query in MongoDB. "$in" can be used to query for a variety of values for a single key. "$or" is more general; it can be used to query for any of the given values across multiple keys. This matches documents with a "user_id" equal to 12345, and documents with a "user_id" equal to "joe" .

How do I select a specific value in MongoDB?

You can select a single field in MongoDB using the following syntax: db. yourCollectionName. find({"yourFieldName":yourValue},{"yourSingleFieldName":1,_id:0});

How do I query an array of objects in MongoDB?

Use the Array Index to Query for a Field in the Embedded Document. Using dot notation, you can specify query conditions for field in a document at a particular index or position of the array. The array uses zero-based indexing. When querying using dot notation, the field and index must be inside quotation marks.


I'd suggest a schema change so that you can actually do reasonable queries in MongoDB.

From:

{
    "userId": "12347",
    "settings": {
        "SettingA": "blue",
        "SettingB": "blue",
        "SettingC": "green"
    }
}

to:

{
    "userId": "12347",
    "settings": [
        { name: "SettingA", value: "blue" },
        { name: "SettingB", value: "blue" },
        { name: "SettingC", value: "green" }
    ]    
}

Then, you could index on "settings.value", and do a query like:

db.settings.ensureIndex({ "settings.value" : 1})

db.settings.find({ "settings.value" : "blue" })

The change really is simple ..., as it moves the setting name and setting value to fully indexable fields, and stores the list of settings as an array.

If you can't change the schema, you could try @JohnnyHK's solution, but be warned that it's basically worst case in terms of performance and it won't work effectively with indexes.


If you don't know what the keys will be and you need it to be interactive, then you'll need to use the (notoriously performance challenged) $where operator like so (in the shell):

db.test.find({$where: function() { 
    for (var field in this.settings) { 
        if (this.settings[field] == "red") return true;
    }
    return false;
}})

If you have a large collection, this may be too slow for your purposes, but it's your only option if your set of keys is unknown.

MongoDB 3.6 Update

You can now do this without $where by using the $objectToArray aggregation operator:

db.test.aggregate([
  // Project things as a key/value array, along with the original doc
  {$project: {
    array: {$objectToArray: '$things'},
    doc: '$$ROOT'
  }},

  // Match the docs with a field value of 'red'
  {$match: {'array.v': 'red'}},

  // Re-project the original doc
  {$replaceRoot: {newRoot: '$doc'}}
])

Sadly, none of the previous answers address the fact that mongo can contain nested values in arrays or nested objects.

THIS IS THE CORRECT QUERY:

{$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")
}}

Since calling typeof on array or nested object will return 'object' this means that the query will iterate on all nested elements and will iterate through all of them until the key with value will be found.

You can check previous answers with a nested value and the results will be far from desired.

Stringifying the whole object is a hit on performance since it has to iterate through all memory sectors one by one trying to match them. And creates a copy of the object as a string in ram memory (both inefficient since query uses more ram and slow since function context already has a loaded object).

The query itself can work with objectId, string, int and any basic javascript type you wish.