1

I'm new to MongoDB and have been having a bit of difficulty setting up a particular query for a new project I'm working on.

I have a data structure that looks like this (simplified version):

games: {_id: ..., scenes: [{_id: ..., views: [{_id: ...}]}]}

(i.e. games contains a collection of scenes, scenes contains a collection of views).

What I want to query here is a particular view object. I suppose the answer involves using $elemMatch, but how do I set this up? After a bit of research + playing around, I know I can do this to get the scene:

db.collection("games").findOne({
    _id: ObjectId(req.params.gid)}, 
    {
    scenes: {
        $elemMatch: {_id: ObjectId(req.params.sid)}
    }
}...

But how do I extend this so that it only pulls the particular view I'm interested in (by _id)?

I guess I could always find the view object I'm looking for manually using a for loop, which brings up another question. Wrt performance, is better to do queries like this using Mongo, or manually by pulling the entire document to loop through collections?

2
  • You can't get only the object within the array. You have to pull the entire document. If you want subdocuments on their own, you'll need to use the aggregation framework. Commented Sep 24, 2016 at 15:29
  • @cdbajorin When you say "on their own" do you mean that it's impossible to filter out views with _id values that I don't want? Or do you mean the query I'm looking for would give me a structure something like result.scenes[0].views[0]? I'm okay with the latter. Commented Sep 24, 2016 at 15:36

2 Answers 2

4

If your collection is not big, and this operation is relatively rare, then it may be fine to do it with the aggregation framework. But if this operation is frequent and performance-critical then I'd say go with application-level querying. In any case, this is how you'd do it with aggregation and $unwind:

So if this is your collection:

> db.col.find().pretty()
{
    "_id" : ObjectId("57e6a5404897ec06f1c3d86f"),
    "games" : [
        {
            "_id" : "game1",
            "scenes" : [
                {
                    "_id" : "scene1",
                    "views" : [
                        {
                            "_id" : "view1"
                        }
                    ]
                }
            ]
        }
    ]
}
{
    "_id" : ObjectId("57e6a5d24897ec06f1c3d870"),
    "games" : [
        {
            "_id" : "game1",
            "scenes" : [
                {
                    "_id" : "scene11",
                    "views" : [
                        {
                            "_id" : "view111"
                        },
                        {
                            "_id" : "view112"
                        },
                        {
                            "_id" : "view113"
                        }
                    ]
                },
                {
                    "_id" : "scene12",
                    "views" : [
                        {
                            "_id" : "view121"
                        }
                    ]
                }
            ]
        },
        {
            "_id" : "game2",
            "scenes" : [
                {
                    "_id" : "scene21",
                    "views" : [
                        {
                            "_id" : "view211"
                        }
                    ]
                }
            ]
        }
    ]
}

and let's say you want to find the view with ID "view112", you can do:

db.col.aggregate([
    { $unwind: "$games"},
    { $unwind: "$games.scenes"},
    { $unwind: "$games.scenes.views"},
    {
        $match: {
            "games.scenes.views._id": "view112"
        }
    }
])

and you'll get:

{
    "_id": ObjectId("57e6a5d24897ec06f1c3d870"),
    "games": {
        "_id": "game1",
        "scenes": {
            "_id": "scene11",
            "views": {
                "_id": "view112"
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

This is cool. Does this require that this particular view ID doesn't exist in any other scene? Any tips on creating indexes for this query?
If this view ID will appear in other places than you'll get multiple elements in the response. That being said, you can add criteria to the $match directive in order to focus on specific game, view, scene, etc. As for your other question, indexes won't help here because the matching is on a collection which is not the original collection, but the result of all the unwinds so it cannot be indexed
Since you know the view id in the first place, you should have a $match at the top of the aggregation to filter only the games that have that view.
0

I don't get it, you can query directly with

db.collection("games").findOne({"scenes.views._id":ObjectId(req.params.vid)},{_id:0,"scenes.$.views":1});

It should remove other scenes from the scene (pun intended). But yeah It still would provide you with multiple views since $ can be used only once in any expression. There I think looping is the key.

I would suggest against aggregate, they are costly. As for to achieve this with query or get the document and loop through it. I for sure would go for achieving it through query, the less data you fetch the better the performance.

3 Comments

Did you mean to compare scenes.views._id with req.params.vid (opposed to sid, which is the param I'm using for scene)? What does the _id:0 do? Note that view IDs are also ObjectId objects.
Yeah it should be req.params.vid, my bad, updated the answer. if it's object Id, it is guaranteed to be unique.
for the second part, _id:0 is telling that we don't need _id in the results. it's projection strategy. You can set what you want and what you don't want with 1 and 0 respectively.

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.