mongodb

mongodb-query

aggregation-framework

mongoose

I have a question about querying nested documents. I tried to search but nothing answered my question or I am maybe overlooking it. I have structure like this:

    {
    "_id" : ObjectId("592aa441e0f8de09b0912fe9"),
    "name" : "Patrick Rothfuss",
    "books" : [ 
    {
        "title" : "Name of the wind",
        "pages" : 400,
        "_id" : ObjectId("592aa441e0f8de09b0912fea")
    }, 
    {
        "title" : "Wise Man's Fear",
        "pages" : 500,
        "_id" : ObjectId("592aa441e0f8de09b0912feb")
    },
    },
    {
    "_id" : ObjectId("592aa441e0f8de09b0912fe9"),
    "name" : "Rober Jordan",
    "books" : [ 
    {
        "title" : "The Eye of the World",
        "pages" : 400,
        "_id" : ObjectId("592aa441e0f8de09b0912fea")
    }, 
    {
        "title" : "The Great Hunt",
        "pages" : 500,
        "_id" : ObjectId("592aa441e0f8de09b0912feb")
    }
    },

And I would like to query for the list of all books in entire colletion of Authors - something like:

"books" : [ 
    {
        "title" : "The Eye of the World",
        "pages" : 400,
        "_id" : ObjectId("592aa441e0f8de09b0912fea")
    }, 
    {
        "title" : "The Great Hunt",
        "pages" : 500,
        "_id" : ObjectId("592aa441e0f8de09b0912feb")
    },
    {
        "title" : "Name of the wind",
        "pages" : 400,
        "_id" : ObjectId("592aa441e0f8de09b0912fea")
    },
    {
        "title" : "Wise Man's Fear",
        "pages" : 500,
        "_id" : ObjectId("592aa441e0f8de09b0912fea")
    }]

Solution 1

You can do this using .aggregate() and predominantly the $unwind pipeline operator:

In modern MongoDB 3.4 and above you can use in tandem with $replaceRoot

Model.aggregate([
  { "$unwind": "$books" },
  { "$replaceRoot": { "newRoot": "$books" } }
],function(err,results) {

})

In earlier versions you specify all fields with $project:

Model.aggregate([
  { "$unwind": "$books" },
  { "$project": {
    "_id": "$books._id",
    "pages": "$books.pages",
    "title": "$books.title"
  }}
],function(err,results) {

})

So $unwind is what you use to deconstruct or "denormalise" the array entries for processing. Effectively this creates a copy of the whole document for each member of the array.

The rest of the task is about returning "only" those fields present in the array.

It's not a very wise thing to do though. If your intent is to only return content embedded within an array of a document, then you would be better off putting that content into a separate collection instead.

It's far better for performance, pulling apart a all documents from a collection with the aggregation framework, just to list those documents from the array only.

Solution 2

According to above mentioned description please try executing following query in MongoDB shell.

db.collection.aggregate(

    // Pipeline
    [
        // Stage 1
        {
            $unwind: "$books"
        },

        // Stage 2
        {
            $group: {
              _id:null,
              books:{$addToSet:'$books'}
            }
        },

        // Stage 3
        {
            $project: {
                books:1,
                _id:0
            }
        },

    ]

);