Search code examples
mongodbprojection

Dynamic mongo projection - a projection that uses a field in the document to determine the projection


Say I have an object like this:

{default: 'x',
 types: {
  x:1,
  y:2,
  z:3
 }
}

Is it possible to select just types.x (ie a projection of {"types.x":1}) without knowing that x is the default beforehand? Making two queries is clearly possible and not what I'm looking for.


Solution

  • Unfortunately this is not available yet as part of the aggregation framework. However, according to this JIRA ticket, it is currently "planned by not scheduled". The only way of doing this currently is by using the map/reduce functionality. If you want to go ahead and use that, it would mean doing something as follows:

    1. Map each document by _id and emit the appropriate key.
    2. Since there will be only one value per key, the reduce function will not get called, but you still need to initialise the variable you use for the reduce function. You can use an empty function or an empty string.
    3. Run map/reduce, saving the results in a collection of your choice.

    In the mongo shell, it looks something as follows:

    var mapper = function() {
        var typeValue = this.types[this.default];
        emit(this._id, typeValue);
    };
    var reducer = "";
    
    db.types.mapReduce(mapper, reducer, { out : "results" } );
    

    If you then query the results collection, you will get something as follows:

    > db.results.find();
    { "_id" : ObjectId("53d21a270dcfb83c7dba8da9"), "value" : 1 }
    

    If you want to know what the default value was, you can modify the mapper function in order to return the key as a value as well. It would look something like this:

    var mapper = function() {
        var typeValue = this.types[this.default],
            typeKey = "types." + this.default;
    
        emit(this._id, { key : typeKey, val : typeValue } );
    };   
    

    When run, it would produce results that look as follows:

    > db.results.find().pretty();
    {
        "_id" : ObjectId("53d21a270dcfb83c7dba8da9"),
        "value" : {
            "key" : "types.x",
            "val" : 1
        }
    }
    

    Note that this is probably a much more convoluted solution than you might want, but it's the only way to do this using MongoDB without adding more logic to your application.