0

I am trying to search within sub documents. This is my structure of my document:

{
    _id: <ObjectID>,
    email: ‘test@emample.com’,
    password: ‘12345’,
    images: [
        {
            title: ‘Broken Hand’,
            description: ‘Here is a full description’,
            comments: [
                {
                    comment: ‘Looks painful’,
                }
            ],
            tags: [‘hand’, ‘broken’]
         }  
    ]
}

And i want to be able to find all images from all users that have a specific tag, but the query i am using is only returning the first image it finds with that tag:

db.site_users.find({'images.tags': "broken"}, {images: 1, images: {$elemMatch: { 'tags': 'broken'}}}).pretty()

Can someone please point me in the right direction to how i can get all the images?

samcooper11
  • 265
  • 2
  • 10
  • 20
  • Your elem match seems to be in the project section instead of the filter section (But I might be mistaking). Try : `db.site_users.find({'images.tags': "broken", { images: {$elemMatch: { 'tags': 'broken'}}}, {images: 1}).pretty()` ? – Ludovic Migneault Apr 14 '16 at 17:36

2 Answers2

0

You can use the aggregation framework for that:

db.site_users.aggregate([
    {$unwind: "$images"},
    {$match:{
        "images.tags": "broken"
    }}
])
joao
  • 4,067
  • 3
  • 33
  • 37
0

The query is good as it matches the document, but the "projection" is outside the scope of what you can do with .find(), you need .aggregate() and some care taken to not remove the "images" items from the array and only the non matching "tags".

Ideally you do this with MongoDB 3.2 using $filter inside $project:

db.site_users.aggregate([
    { "$match": { "images.tags": "broken" }},
    { "$project": {
        "email": 1,
        "password": 1,
        "images": {
            "$filter": {
                "input": "$images",
                "as": "image",
                "cond": {
                    "$setIsSubSet": [["broken"], "$$image.tags"]
                }
            }
        }
    }}
])

Or possibly using $map and $setDifference which is also compatible with MongoDB 2.6, as long as the "images" content is "unique" for each entry. This is due to the "set" operation, in which "sets" are "unique":

db.site_users.aggregate([
    { "$match": { "images.tags": "broken" }},
    { "$project": {
        "email": 1,
        "password": 1,
        "images": {
            "$setDifference": [
                { "$map": {
                    "input": "$images",
                    "as": "image",
                    "in": {
                        "$cond": {
                            "if": { "$setIsSubSet": [["broken"], "$$image.tags" ] },
                            "then": "$$image",
                            "else": false
                        }
                    }
                }},
                [false]
            ]
        }
    }}
])

It can be done in earlier versions of MongoDB but is possibly best avoided due to the cost of processing $unwind on the array:

db.site_users.aggregate([
    { "$match": { "images.tags": "broken" }},
    { "$unwind": "$images" },
    { "$match": { "images.tags": "broken" }},
    { "$group": {
        "_id": "$_id",
        "email": { "$first": "$email" },
        "password": { "$first": "$password" },
        "images": { "$push": "$images" }
    }}       
])

Since there is usually a considerable cost in using $unwind for this purpose where you are not "aggregating" anything, then if you don't have a modern version where the other practical approaches are available, it's often best to accept "filtering" the array content itself in client code rather than on the server.

So you should only resort to $unwind for this case where the array entries would be "significantly" reduced by order of removing the non-matching items. Otherwise the cost of processing is likely greater than the network cost of transferring the data, and any benefit is negated.

If you don't have a modern version, then get one. The features make all the difference to what is practical and performant.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317