Search code examples
mongodbembedded-documents

$elemMatch on embedded documents


Here is what I am working with:

{
    "_id": ObjectId("53b4402ae1382335c95952d3"),
    "created": ISODate("2014-07-02T10:23:54.245Z"),
    "modified": ISODate("2011-11-28T10:06:25.186Z"),
    "source": [{
        "instances": [{
            "analyst": "harry",
            "date": ISODate("2014-07-02T10:23:54.242Z"),
            "reference": "abb32"
        },
        {
            "analyst": "david",
            "date": ISODate("2014-07-02T10:33:02.296Z"),
            "reference": "can26"
        }],
        "name": "Apples"
    },
    {
        "instances": [{
            "analyst": "critsAPI",
            "date": ISODate("2011-11-28T10:06:25.156Z"),
            "reference": "z561c"
        }],
        "name": "Oranges"
    }],
    "releasability": [],
}

What I would like is a count of every document which has an "instances date " in a certain range (lets say a month) along with a certain name. I have used two queries with "elemMatch" and unfortunately I am not getting the results I expect. Here is what I have tried using elemMatch and instances.date:

    collection.find(
                    {
                    'source':
                      {
                        '$elemMatch' : {
                              'name':'Apples',
                              'instances.date' : {'$lte' :   ISODate("2014-07-30T00:00:00Z") ,    
                                                 '$gte' :  ISODate("2014-07-01T00:00:00Z")}
                       }
                       }
                       }
                        ).count()

Here is with a nested elemMatch :

    collection.find(
                    {
                    'source':
                      {
                        '$elemMatch' : {
                              'name':'Apples',
                              'instances': {
                                  '$elemMatch' : {
                                       'date' : {'$lte' :   ISODate("2014-07-30T00:00:00Z") ,    
                                                 '$gte' :  ISODate("2014-07-01T00:00:00Z")}
                       }
                       }
                       }
                       }
                       }
                        ).count()

Thanks in advance.


Solution

  • I'm guessing that your are getting a count() of 1 since the count() method returns the number of documents, not the number of array elements.

    To get a result of 2 (based on the above document) you need to use the Aggregation Framework.

    Please try something like the following:

        db.coll1.aggregate([ 
            {$unwind : "$source"},
            {$unwind : "$source.instances"},
            {$match: { 
                $and: [
                {"source.name" : "Apples"},
                {"source.instances.date" : {"$lte" : ISODate("2014-07-30T00:00:00Z")}},
                {"source.instances.date" : {"$gte" : ISODate("2014-07-01T00:00:00Z")}}
                      ]
            }},
            {$project: {
                name1: "$source.name",
                date1: "$source.instances.date"
            }},
                {$group : {_id: "$name1", name1: {$sum:1}}}
            ])
        
    and I get the following in the shell { "result" : [ { "_id" : "Apples", "name1" : 2 } ], "ok" : 1 }

    The $unwind breaks the array into separate docs, the $match does the selection, the $project defines the result fields (similar toSELECT clause in SQL), the $group sums the entries (similar to 'GROUP BY` in SQL)

    These individual steps in the aggregation pipeline sequentially filters/transforms a result set of documents.

    Hope that helps