Kevin
Kevin

Reputation: 3631

In a Mongo collection, how do you query for a specific object in an array?

I'm trying to retrieve an object from an array in mongodb. Below is my document:

{
    "_id" : ObjectId("53e9b43968425b29ecc87ffd"),
    "firstname" : "john",
    "lastname" : "smith",
    "trips" : [ 
        {
            "submitted" : 1407824585356,
            "tripCategory" : "staff",
            "tripID" : "1"
        }, 
        {
            "tripID" : "2",
            "tripCategory" : "volunteer"
        }, 
        {
            "tripID" : "3",
            "tripCategory" : "individual"
        }
    ]
}

My ultimate goal is to update only when trips.submitted is absent so I thought I could query and determine what the mongo find behavior would look like if I used the $and query operator. So I try this:

db.users.find({
    $and: [
      { "trips.tripID": "1" },
      { "trips": { $elemMatch: { submitted: { $exists: true } } } }
    ]
  },
  { "trips.$" : 1 }   //projection limits to the FIRST matching element
)

and I get this back:

{
    "_id" : ObjectId("53e9b43968425b29ecc87ffd"),
    "trips" : [ 
        {
            "submitted" : 1407824585356,
            "tripCategory" : "staff",
            "tripID" : "1"
        }
    ]
}

Great. This is what I want. However, when I run this query:

db.users.find({
    $and: [
      { "trips.tripID": "2" },
      { "trips": { $elemMatch: { submitted: { $exists: true } } } }
    ]
  },
  { "trips.$" : 1 }   //projection limits to the FIRST matching element
)

I get the same result as the first! So I know there's something odd about my query that isn't correct. But I dont know what. The only thing I've changed between the queries is "trips.tripID" : "2", which in my head, should have prompted mongo to return no results. What is wrong with my query?

Upvotes: 1

Views: 119

Answers (2)

Ian Mercer
Ian Mercer

Reputation: 39277

If you know the array is in a specific order you can refer to a specific index in the array like this:-

db.trips.find({"trips.0.submitted" : {$exists:true}})

Or you could simply element match on both values:

db.trips.find({"trips" : {$elemMatch : {"tripID" : "1", 
                                        "submitted" : {$exists:true}
              }}})

Your query, by contrast, is looking for a document where both are true, not an element within the trips field that holds for both.

Upvotes: 2

AxxE
AxxE

Reputation: 843

The output for your query is correct. Your query asks mongo to return a document which has the given tripId and the field submitted within its trips array. The document you have provided in your question satisfies both conditions for both tripIds. You are getting the first element in the array trips because of your projection.

I have assumed you will be filtering records by the person's name and then retrieving the elements inside trips based on the field-exists criteria. The output you are expecting can be obtained using the following:

db.users.aggregate(
    [
        {$match: 
            {
                "firstname" : "john",
                "lastname" : "smith"
            }
        },
        {$unwind: "$trips"},
        {$match:
             { 
                 "trips.tripID": "1" ,
                 "trips.submitted": { $exists: true }
             }
        }
    ]
)

The aggregation pipeline works as follows. The first $match operator filters one document (in this case the document for john smith) The $unwind operator in mongodb aggregation unwinds the specified array (trips in this case), in effect denormalizing the sub-records associated with the parent records. The second $match operator filters the denormalized/unwound documents further to obtain the one required as per your query.

Upvotes: 1

Related Questions