Arun Yegi
Arun Yegi

Reputation: 73

how to query nested array of objects in mongodb?

i am trying to query nested array of objects in mongodb from node js, tried all the solutions but no luck. can anyone please help this on priority?

I have tried following :

{
              "name": "Science",
              "chapters": [
                 {
                     "name": "ScienceChap1",
                     "tests": [
                        {
                            "name": "ScienceChap1Test1",
                            "id": 1,
                            "marks": 10,
                            "duration": 30,
                            "questions": [
                               {
                                   "question": "What is the capital city of New Mexico?",
                                   "type": "mcq",
                                   "choice": [
                                      "Guadalajara",
                                      "Albuquerque",
                                      "Santa Fe",
                                      "Taos"
                                   ],
                                   "answer": [
                                      "Santa Fe",
                                      "Taos"
                                   ]
                               },
                               {
                                   "question": "Who is the author of beowulf?",
                                   "type": "notmcq",
                                   "choice": [
                                      "Mark Twain",
                                      "Shakespeare",
                                      "Abraham Lincoln",
                                      "Newton"
                                   ],
                                   "answer": [
                                      "Shakespeare"
                                   ]
                               }
                            ]
                        },
                        {
                            "name": "ScienceChap1test2",
                            "id": 2,
                            "marks": 20,
                            "duration": 30,
                            "questions": [
                               {
                                   "question": "What is the capital city of New Mexico?",
                                   "type": "mcq",
                                   "choice": [
                                      "Guadalajara",
                                      "Albuquerque",
                                      "Santa Fe",
                                      "Taos"
                                   ],
                                   "answer": [
                                      "Santa Fe",
                                      "Taos"
                                   ]
                               },
                               {
                                   "question": "Who is the author of beowulf?",
                                   "type": "notmcq",
                                   "choice": [
                                      "Mark Twain",
                                      "Shakespeare",
                                      "Abraham Lincoln",
                                      "Newton"
                                   ],
                                   "answer": [
                                      "Shakespeare"
                                   ]
                               }
                            ]
                        }
                     ]
                 }
              ]
          }

Here is what I've tried so far but still can't get it to work

db.quiz.find({name:"Science"},{"tests":0,chapters:{$elemMatch:{name:"ScienceCh‌​ap1"}}})
db.quiz.find({ chapters: { $elemMatch: {$elemMatch: { name:"ScienceChap1Test1" } } }}) 
db.quiz.find({name:"Science"},{chapters:{$elemMatch:{$elemMatch:{name:"Scienc‌​eChap1Test1"}}}}) ({ name:"Science"},{ chapters: { $elemMatch: {$elemMatch: { name:"ScienceChap1Test1" } } }})

Upvotes: 4

Views: 3043

Answers (2)

Abdullah Rasheed
Abdullah Rasheed

Reputation: 3752

Aggregation Framework

You can use the aggregation framework to transform and combine documents in a collection to display to the client. You build a pipeline that processes a stream of documents through several building blocks: filtering, projecting, grouping, sorting, etc.

If you want get the mcq type questions from the test named "ScienceChap1Test1", you would do the following:

db.quiz.aggregate(
    //Match the documents by query. Search for science course
    {"$match":{"name":"Science"}},

    //De-normalize the nested array of chapters.
    {"$unwind":"$chapters"},
    {"$unwind":"$chapters.tests"},

    //Match the document with test name Science Chapter
    {"$match":{"chapters.tests.name":"ScienceChap1test2"}},

    //Unwind nested questions array
    {"$unwind":"$chapters.tests.questions"},

    //Match questions of type mcq
    {"$match":{"chapters.tests.questions.type":"mcq"}}
).pretty()

The result will be:

{
    "_id" : ObjectId("5629eb252e95c020d4a0c5a5"),
    "name" : "Science",
    "chapters" : {
        "name" : "ScienceChap1",
        "tests" : {
            "name" : "ScienceChap1test2",
            "id" : 2,
            "marks" : 20,
            "duration" : 30,
            "questions" : {
                "question" : "What is the capital city of New Mexico?",
                "type" : "mcq",
                "choice" : [
                    "Guadalajara",
                    "Albuquerque",
                    "Santa Fe",
                    "Taos"
                ],
                "answer" : [
                    "Santa Fe",
                    "Taos"
                ]
            }
        }
    }
}

$elemMatch doesn't work for sub documents. 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.

Upvotes: 4

Abdullah Rasheed
Abdullah Rasheed

Reputation: 3752

You should try the following queries in the mongodb simple javascript shell.

There could be Two Scenarios.

Scenario One

If you simply want to return the documents that contain certain chapter names or test names for example just one argument in find will do.

For the find method the document you want to be returned is specified by the first argument. You could return documents with the name Science by doing this:

db.quiz.find({name:"Science"})

You could specify criteria to match a single embedded document in an array by using $elemMatch. To find a document that has a chapter with the name ScienceChap1. You could do this:

db.quiz.find({"chapters":{"$elemMatch":{"name":"ScienceChap1"}}})

If you wanted your criteria to be a test name then you could use the dot operator like this:

db.quiz.find({"chapters.tests":{"$elemMatch":{"name":"ScienceChap1Test1"}}})

Scenario Two - Specifying Which Keys to Return

If you want to specify which keys to Return you can pass a second argument to find (or findOne) specifying the keys you want. In your case you can search for the document name and then provide which keys to return like so.

db.quiz.find({name:"Science"},{"chapters":1})

//Would return
{
    "_id": ObjectId(...),
    "chapters": [
        "name": "ScienceChap2",
        "tests: [..all object content here..]
}

If you only want to return the marks from each test object you can use the dot operator to do so:

db.quiz.find({name:"Science"},{"chapters.tests.marks":1})

//Would return
{
    "_id": ObjectId(...),
    "chapters": [
        "tests: [
            {"marks":10},
            {"marks":20}
         ]
}

If you only want to return the questions from each test:

db.quiz.find({name:"Science"},{"chapters.tests.questions":1})

Test these out. I hope these help.

Upvotes: 0

Related Questions