Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Query the map size in MongoDB

Tags:

mongodb

Suppose you have a map of variable size in mongodb. How can you get documents that have only a certain map size, for example >2?

{map:{k1:v1, k2:v2, k3,v3}} 
{map:{k1:v1, k2:v2}}
like image 384
Roland Kofler Avatar asked Oct 21 '25 23:10

Roland Kofler


1 Answers

With MongoDB 3.6 and greater:

Use the $expr operator in your query, this allows you to use aggregation framework operators in particular the $objectToArray operator that converts the map subdocument to an array of key values i.e.

{ map: {
    k1:v1, 
    k2:v2, 
    k3,v3
}}

is transformed to the array

{ map: [
    { k: 'k1', v: 'v1' }, 
    { k: 'k2', v: 'v2' }, 
    { k: 'k3', v: 'v3' }
]}

With the array you can then use $size to get the length and compare using the comparison query operator $gt.

An example follows which shows the complete query:

db.test.find({
    "$expr": {
        "$gt": [
            { "$size": { "$objectToArray": "$map" } },
            2
        ]
    }
})

For MongoDB versions which do not have support for the above operators, you'd need to pre-calculate such map size if you want to do queries on them later so consider creating a new field "keyCount" which holds the number of keys for the map subdocument. Consider the following demonstration:

Populate test collection

db.test.insert([    
    {   
        "_id" : 1,  
        "map" : {
            "k1" : "v1",
            "k2" : "v2",
            "k3" : "v3"
        }
    },
    {       
        "_id" : 2,
        "map" : {
            "k1" : "v1",
            "k2" : "v2"
        }
    }
 ]);

As with the current design, you would need a mechanism to get all count of the keys inside the map document. This is possible through Map-Reduce. The following mapreduce operation will populate a separate collection with the new field "keyCount" added:

var mr = db.runCommand({
    "mapreduce": "test",
    "map" : function() {
        var obj = this;
        obj['keyCount'] = Object.keys(this.map).length;
        emit(this._id, obj); 
    },
    "reduce" : function(key, stuff) { return null; }, 
    "out": "my_collection" + "_keys"
})

To get documents that have only a certain map size, for example > 2, run the query on the resulting collection:

db[mr.result].find({ "value.keyCount": { "$gt": 2 } });

Map Reduce Output

/* 0 */
{
    "result" : "my_collection_keys",
    "timeMillis" : 7,
    "counts" : {
        "input" : 2,
        "emit" : 2,
        "reduce" : 0,
        "output" : 2
    },
    "ok" : 1
}

Query Output

/* 0 */
{
    "_id" : 1,
    "value" : {
        "_id" : 1,
        "map" : {
            "k1" : "v1",
            "k2" : "v2",
            "k3" : "v3"
        },
        "keyCount" : 3
    }
}
like image 101
chridam Avatar answered Oct 24 '25 19:10

chridam