-1

Suppose we had something like the following document, but we wanted to return only the fields that had numeric information:

{
    "_id" : ObjectId("52fac254f40ff600c10e56d4"),
    "name" : "Mikey",
    "list" : [ 1, 2, 3, 4, 5 ],
    "people" : [ "Fred", "Barney", "Wilma", "Betty" ],
    "status" : false,
    "created" : ISODate("2014-02-12T00:37:40.534Z"),
    "views" : 5
}

Now I know that we can query for fields that match a certain type by use of the $type operator. But I'm yet to stumble upon a way to $project this as a field value. So if we looked at the document in the "unwound" form you would see this:

{
    "_id" : ObjectId("52fac254f40ff600c10e56d4"),
    "name" : 2,
    "list" : 16,
    "people" : 2
    "status" : 8,
    "created" : 9,
    "views" : 16
}

The final objective would be list only the fields that matched a certain type, let's say compare to get the numeric types and filter out the fields, after much document mangling, to produce a result as follows:

{
    "_id" : ObjectId("52fac254f40ff600c10e56d4"),
    "list" : [ 1, 2, 3, 4, 5 ],
    "views" : 5
}

Does anyone have an approach to handle this.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317

1 Answers1

2

There are a few issues that make this not practical:

  1. Since the query is a distinctive parameter from the ability to do a projection, this isn't possible from a single query alone, as the projection cannot be influenced by the results of the query
  2. As there's no way with the aggregation framework to iterate fields and check type, that's also not an option

That being said, there's a slightly whacky way of using a Map-Reduce that does get similar answers, albeit in a Map-Reduce style output that's not awesome:

map = function() {
    function isNumber(n) {
      return !isNaN(parseFloat(n)) && isFinite(n);
    }

    var numerics = [];
    for(var fn in this) {
        if (isNumber(this[fn])) {
            numerics.push({f: fn, v: this[fn]});
        }
        if (Array.isArray(this[fn])) {
            // example ... more complex logic needed
            if(isNumber(this[fn][0])) {
                numerics.push({f: fn, v: this[fn]});
            }
        }
    }
    emit(this._id, { n: numerics });
};

reduce = function(key, values) {
  return values;  
};

It's not complete, but the results are similar to what you wanted:

"_id" : ObjectId("52fac254f40ff600c10e56d4"),
 "value" : {
         "n" : [
                 {
                         "f" : "list",
                         "v" : [
                                 1,
                                 2,
                                 3,
                                 4,
                                 5
                         ]
                 },
                 {
                         "f" : "views",
                         "v" : 5
                 }
         ]
 }

The map is just looking at each property and deciding whether it looks like a number ... and if so, adding to an array that will be stored as an object so that the map-reduce engine won't choke on array output. I've kept it simple in the example code -- you could improve the logic of numeric and array checking for sure. :)

Of course, it's not live like a find or aggregation, but as MongoDB wasn't designed with this in mind, this may have to do if you really wanted this functionality.

WiredPrairie
  • 58,954
  • 17
  • 116
  • 143
  • Yes. That was my thinking as well. This came from another question which was basically wanting to filter the fields this way. Maybe a **$type** operator in aggregation would be nice, much along the line of the date operators, but I'm really not sure about the wide utility of such a thing. – Neil Lunn Feb 12 '14 at 04:52