3

Is it possible to retrieve the position of an array element that matches the query ?

For example, I have a collection with documents like this:

{"_id":ObjectId("560122469431950bf55cb095"), "otherIds": [100, 103, 108, 104]}

And I would like to obtain a query result like this for the otherId->108 :

{"_id":ObjectId("560122469431950bf55cb095"),"position":3}

Is it possible to get something like this? Thanks in advance!

Blakes Seven
  • 49,422
  • 14
  • 129
  • 135
hiamex
  • 295
  • 2
  • 11

1 Answers1

1

Well you can always run a mapReduce for this and match the array index via .indexOf():

db.collection.mapReduce(
    function() {
        emit(this._id,{ "position": this.otherIds.indexOf(108) });
    },
    function() {},
    {
        "out": { "inline": 1 },
        "query": { "otherIds": 108 }
    }
)

Or for possible "multiple" matches then use .map() with the optional "index" parameter:

db.collection.mapReduce(
    function() {
        emit(this._id,{ 
            "positions": this.otherIds.map(function(el,idx) {
                return (el == 108) ? idx : -1;
            }).filter(function(el) {
                return el != -1;
            })
       });
    },
    function() {},
    {
        "out": { "inline": 1 },
        "query": { "otherIds": 108 }
    }
)

But of course array indexes start at 0 so if you expect the result to be 3 then you can always add 1 to the index position matched.

It is arguable of course that you simply just return the array in the query response and just match the position(s) of the matched element in client code, and it probably is best handled that way unless you have particularly large arrays.

There is an $arrayElemAt operator currently placed within the development branch of MongoDB, but this unfortunately works the other way around, and rather than returning the position of the matched element it returns the element at a given position. And since there is no present way to determine a current array position or cycle all possible positions it cannot be reverse engineered into providing a positive match at a given position.

It is also arguable that other operators like $map and the upcoming $filter ( also in dev branch ) should have a similar option as a system variable accessible within those commands like their JavaScript and other language equivalents. Then something like $$IDX ( trending from other system variables like $$ROOT ) available then you could do this under .aggregate():

db.collection.aggregate([
    { "$match": { "otherIds": 108 } },
    { "$project": {
        "position": {
            "$filter": {
                "input": {
                    "$map": {
                        "input": "$otherIds",
                        "as": "el",
                        "in": {
                            "$cond": [
                                { "$eq": [ "$$el", 108 ] },
                                "$$IDX",
                                -1
                            ]
                        }
                    }
                },
                "as": "el",
                "cond": { "$ne": ["$$el", -1] }
            }
        }
    }}
])

But that is yet to happen, though it would be nice to see, and seemingly not a hard ask as both have internal workings that keep the current index position anyway, so it should just be a matter of exposing that for use as an accessible variable.

Blakes Seven
  • 49,422
  • 14
  • 129
  • 135
  • Thank you very much Blakes Seven, I don't think that performing a map reduce is the best approach for my case, I would try to save data in another disposition. – hiamex Sep 24 '15 at 10:56
  • @hiamex It would probably be better if you asked a question that instead explained why you need this matched index behavior. As stated, in general it is probably best to just work out the position in the client code unless your arrays are particularly large and you have something to gain from doing otherwise. – Blakes Seven Sep 24 '15 at 10:59