DevEx
DevEx

Reputation: 4561

How to select items in embedded documents

How can I select all items in an embedded documents in mongodb? for example, how can I select all the items inside books ?

{
 "_id": 67
 "Item_1":
       {
        "books": [
            {"genre": "Classics", "author": "something"},
            {"genre": "Fiction", "author": "something"}
            {"genre": "Fiction", "author": "something"}
         ]¨
       }

}

Upvotes: 0

Views: 109

Answers (1)

Neil Lunn
Neil Lunn

Reputation: 151112

If you are looking to "flatten" out your schema a little then you can do this with the aggregation framework and use of the $project pipeline operator. This has a few more features than the projection available to .find() and in this sample you also use $unwind to "de-normalize" your arrays as documents:

db.collection.aggregate([
    { "$unwind": "$Item_1.books" },
    { "$project": {
        "genre": "$Item_1.books.genre", "author": "$Item_1.books.author"
    }}
])

That gives you a result like this:

{ "_id": 67, "genre": "Classics", "author": "something" },
{ "_id": 67, "genre": "Fiction", "author": "something" }
{ "_id": 67, "genre": "Fiction", "author": "something" }

It does make a certain amount of sense to keep something referring to the original document in there and _id will be there unless you exclude it. But you can always exclude if your really want.

Additionally you can filter this list for multiple results, for example just to get the "Fiction" genre:

db.collection.aggregate([
    { "$unwind": "$Item_1.books" },
    { "$match": { "Item_1.books.genre": "Fiction" } }
    { "$project": {
        "genre": "$Item_1.books.genre", "author": "$Item_1.books.author"
    }}
])

Gives you:

{ "_id": 67, "genre": "Fiction", "author": "something" }
{ "_id": 67, "genre": "Fiction", "author": "something" }

Over multiple documents it may also make sense to use an additional $match first in the pipeline, in order to reduce the number of documents that did not contain a "Fiction" genre in their array.

Another form with MongoDB 2.6 may also be to do this:

db.collection.aggregate([
    { "$match": { "Item_1.books.genre": "Fiction" } },
    { "$project": {
        "books": {
            "$setDifference: [
                { "$map": {
                    "input ": "$Item_1.books",
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.genre", "Fiction" ] },
                            "$$el",
                            false
                        ]
                    }
                }},
                [false]
            ]
        }
    }},
    { "$unwind": "$books" },
    { "$project": {
        "genre": "$books.genre",
        "author": "$books.fiction"
    }}
])

This moves the $match on the array elements to an "inline" version to filter the contents of the array using $map and $setDifference. It is just another approach and utility may vary considering the size of the array, where small sizes will make little difference.

As a final note, your schema appears to be keyed off of that "Item_1" key there as an object. If it is your actual intent to have many of these and indeed search or combine results across them, then a schema change would greatly benefit you in querying:

{
    "_id": 67
    "books": [
        {"type": "Item1", "genre": "Classics", "author": "something"},
        {"type": "Item1", "genre": "Fiction", "author": "something"},
        {"type": "Item1", "genre": "Fiction", "author": "something"},
        {"type": "Item2", "genre": "Fiction", "author": "something"}
    ]¨

}

That makes it easy to combine across the various keys or indeed separate or group without having to specify directly down the path to a field as you would presently have to do.

Upvotes: 1

Related Questions