2

I hope someone can shed some light on this issue that I have, it's driving me crazy to a point that I have been spending the past three days, learning more and more about mongoDB but still can't figure out this simple query.

What I need to do is to get the object containing the "carId" = "3C".

In other words the object that I want the query to return is:

            {
                "carId" : "3C", 
                "_id" : ObjectId("51273329b64f07a40ef1c15e")
            }

Here is the dataset (cars):

   { 
        "_id" : ObjectId("56223329b64f07a40ef1c15c"), 
        "username" : "john", 
        "email" : "john@john.com", 
        "accounts" : [
            {
                "_id" : ObjectId("56322329b61f07a40ef1c15d"), 
                "cars" : [
                    {
                        "carId" : "6A", 
                        "_id" : ObjectId("56323329b64f07a40ef1c15e")
                    },
                    {
                        "carId" : "6B", 
                        "_id" : ObjectId("56323329b64f07a40ef1c15e")
                    }
                ]
            }
        ]
    },
    { 
        "_id" : ObjectId("56223125b64f07a40ef1c15c"), 
        "username" : "paul", 
        "email" : "paul@paul.com", 
        "accounts" : [
            {
                "_id" : ObjectId("5154729b61f07a40ef1c15d"), 
                "cars" : [
                    {
                        "carId" : "5B", 
                        "_id" : ObjectId("56323329854f07a40ef1c15e")
                    }
                ]
            },
            {
                "_id" : ObjectId("56322117b61f07a40ef1c15d"), 
                "cars" : [
                    {
                        "carId" : "6G", 
                        "_id" : ObjectId("51212929b64f07a40ef1c15e")
                    },
                    {
                        "carId" : "3C", 
                        "_id" : ObjectId("51273329b64f07a40ef1c15e")
                    },
                    {
                        "carId" : "4N", 
                        "_id" : ObjectId("51241279b64f07a40ef1c15e")
                    }
                ]
            }
        ]
    }

Please note that I have two nested arrays, and apparently MongoDb lacks when it comes to dealing with Projections with deep arrays. The $ operator can only be used once in a projection; leaving with no clues as how to to achieve this simple task.

So again I want to find --only-- the document that has "carId" : "3C" and only return the immediate obj containing the "carId" : "3C". but not the parent objects.

Any help would be so much appreciated. Possibly using either direct MongoDb or Mongoose. Mongoose would be preferred.

As for reference, I have already covered these other related issues wasn't able to figure it out.

Updating a deep record in MongoDb

How to Update Multiple Array Elements in mongodb

Hope in the future, this question and your solutions will help others.

Community
  • 1
  • 1
Amir Mog
  • 321
  • 1
  • 4
  • 17

2 Answers2

8

Amir,

You must use the Aggregation Framework. You can build a pipeline that processes a stream documents through several building blocks: filtering, projecting,grouping,sorting,etc.

When dealing with nested arrays you will have to use the $unwind command. You can get what you want by doing the following.

db.cars.aggregate(
    //De-normalized the nested array of accounts
    {"$unwind": "$accounts"},
    //De-normalized the nested array of cars
    {"$unwind": "$accounts.cars"},
    //match carId to 3C
    {"$match": {"accounts.cars.carId" : "3C"}},
    //Project the accoutns.cars object only
    {"$project" : {"accounts.cars" : 1}},
    //Group and return only the car object
    {"$group":{"_id":"$accounts.cars"}}
).pretty();

You can use the aggregation framework for "array filtering" by using $unwind .

You can delete each line from the bottom of each command in the aggregation pipeline in the above code to observe the pipelines behavior.

Abdullah Rasheed
  • 3,562
  • 4
  • 33
  • 51
  • Thank you so much, this sounds great; is there a way to achieve the same with Mongoose? – Amir Mog Oct 29 '15 at 21:41
  • Yes. You can use it with mongoose. I'll add to this answer as soon as I get to my laptop. – Abdullah Rasheed Oct 29 '15 at 23:17
  • Just tried this aggregation and it's working like a charm except for one small point. It returns this: { "_id" : ObjectId("56223125b64f07a40ef1c15c"), "accounts" : { "cars" : { "carId" : "3C", "_id" : ObjectId("51273329b64f07a40ef1c15e") } } } instead of the expected object: {"cars" : { "carId" : "3C", "_id" : ObjectId("51273329b64f07a40ef1c15e") } . So from this point, the carId obj is easily accessible with doc.accounts.cars how should this be changed to get the desired result. ...? thanks – Amir Mog Oct 29 '15 at 23:25
  • Once again I would like to thank you so much. You are a hero. -- knowing the Mongoose syntax will help very much also as I'm using mongoose in my code. thanks – Amir Mog Oct 29 '15 at 23:30
  • @AmirMog I just changed the answer to only return the one object. Please let me know if it works. – Abdullah Rasheed Oct 30 '15 at 09:36
  • thanks but I don't think this grouping did the trick correctly. It returned { "cars" : [ { _id: "accounts.cars"} ] } which is not the desired car obj, it should group so that it would return this: { "carId" : "3C", "_id" : ObjectId("51273329b64f07a40ef1c15e") } . Any ideas? thanks – Amir Mog Oct 30 '15 at 17:12
1

Here's an example without the aggregation framework. I don't think there's a way purely from querying that you'll be able to get just the individual nested object you're looking for so you have to do a little post processing work. something like Mongoose may provide a way to do this but I'm not really up on what the Mongoose API's look like currently.

var doc = db.cars.findOne({"accounts.cars" : {$elemMatch: {"carId" : "3C"}}}, {"accounts.cars.$": 1, _id: 0})
var car = doc.accounts[0].cars[0]
ThrowsException
  • 2,586
  • 20
  • 37
  • Would this work without knowing the index of the obj containing "carId" : "3C"? I believe var car = doc.accounts[0].cars[0] would not work, or am I missing something? – Amir Mog Oct 29 '15 at 21:39
  • I tested using the example docs you have. It did work because the projection is only going to return the first element that matches the query. You should probably check for nulls. – ThrowsException Oct 29 '15 at 21:41
  • I just modified the example docs so that the { "carId" : "3C", "_id" : ObjectId("51273329b64f07a40ef1c15e") }, would not be at index 0 in the array. Would this still work? – Amir Mog Oct 29 '15 at 21:54
  • I just checked this and the projection actually returns all three objects inside "cars" array. It doesn't return only one. So trying to access it with index won't help. How can make the projection return the array of "cars" with only one item that being the { "carId" : "3C", "_id" : ObjectId("51273329b64f07a40ef1c15e") } ? thanks – Amir Mog Oct 29 '15 at 22:03
  • 1
    Sorry. Thought the $elemMatch operator would work here but it appears its not going to apply to the cars array, only the accounts array. without restructuring data, Aggregation seems like the way to achieve this. If you mark the other answer as the accepted one ill probably delete this since I don't think this approach will work. – ThrowsException Oct 29 '15 at 23:03
  • Thank you so much for your great help, yes as you also mentioned, the aggregation is the way to do it. But I greatly appreciate your help. Would you possibly know how to convert the aggregate solution above into Mongoose syntax? thanks – Amir Mog Oct 29 '15 at 23:26
  • Not off the top of my head but Mongoose seems to have aggregation support http://mongoosejs.com/docs/api.html#aggregate-js – ThrowsException Oct 30 '15 at 13:08