11

Think of this MongoDB document:

{_id:123, "food":[ "apple", "banana", "mango" ]}

Question: How to get the position of mango in food?

The query should return 2 in above, and don't return the whole document.

Please kindly show the working query.

3 Answers 3

16

Starting from MongoDB version 3.4 we can use the $indexOfArray operator to return the index at which a given element can be found in the array.

$indexOfArray takes three arguments. The first is the name of the array field prefixed with $ sign.

The second is the element and the third optional is the index to start the search at. $indexOfArray returns the first index at which the element is found if the index to start the search at is not specified.


Demo:

> db.collection.insertOne( { "_id" : 123, "food": [ "apple", "mango", "banana", "mango" ] } )
{ "acknowledged" : true, "insertedId" : 123 }
> db.collection.aggregate( [ { "$project": { "matchedIndex": { "$indexOfArray": [ "$food", "mango" ] } } } ] )
{ "_id" : 123, "matchedIndex" : 1 }
> db.collection.aggregate( [ { "$project": { "matchedIndex": { "$indexOfArray": [ "$food", "mango", 2 ] } } } ] )
{ "_id" : 123, "matchedIndex" : 3 }
> db.collection.aggregate( [ { "$project": { "matchedIndex": { "$indexOfArray": [ "$food", "apricot" ] } } } ]  )
{ "_id" : 123, "matchedIndex" : -1 }
Sign up to request clarification or add additional context in comments.

2 Comments

This answer is best for version 3.4+, but if using old version, should follow the answer of @BlakesSeven
How can I match object values using an expression as the second argument? Such as { $indexOfArray: ['$array', { $and: [...]}]} ? Using $and does not seem to work. It seems to want to match the exact shape of the object
5

There really is no other way ( "server side" ) than using mapReduce:

db.collection.mapReduce(
    function() {
        emit(this._id, this.food.indexOf("mango"));
    },
    function() {},   // reducer never gets called since all _id is unique
    { 
        "out": { "inline": 1 },
        "query": { "food": "mango" }
    }
)

It is the only thing that will return something else in a modified form other than the document itself, as well as using the needed JavaScript evaluation in order to determine the answer,

There is unfortunately no "native" operator that will do this.

Unless you need this for real aggregation purposes, then it is better to just do a similar "array index match" in native code in your client when dealing on a "per document" basis.

3 Comments

worked like a charm! But how about the performance on large collection when invoke mapReduce?
@JamesYang I think you need to grasp the point that I don't think you should be doing this anyway unless you actually need to aggregate the result further ( as already stated ). There is no server side alternate ( also already stated ), so you are either stuck with mapReduce in the "server side" case or you process the index of array per document in the client ( as already recommended ).
With {jsMode:true} options, is that good for performance? in your experience
0

In mongo shell (in Robomongo also) I would do the following:

    var food = db.getCollection('yourCollection').findOne({_id: '123'}).food;
    print('Index of mango: ' + food.indexOf('mango'));

or you can save this code in any_file.js and then run from command line:

    mongo your_db any_file.js

It will produce something like that:

    MongoDB shell version: 2.4.9
    connecting to: localhost:27017/your_db
    Index of mango: 2

1 Comment

a solution, but maybe not practicle

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.