Question

I am new to mongodb and I am trying to figure out how to count all the returned query inside an array of documents like below:

"impression_details" : [ 
        {
            "date" : ISODate("2014-04-24T16:35:46.051Z"),
            "ip" : "::1"
        }, 
        {
            "date" : ISODate("2014-04-24T16:35:53.396Z"),
            "ip" : "::1"
        }, 
        {
            "date" : ISODate("2014-04-25T16:22:20.314Z"),
            "ip" : "::1"
        }
]

What I would like to do is count how many 2014-04-24 there are (which is 2). At the moment my query is like this and it is not working:

db.banners.find({
    "impression_details.date":{ 
        "$gte": ISODate("2014-04-24T00:00:00.000Z"), 
        "$lte": ISODate("2014-04-24T23:59:59.000Z")
    }
}).count()

Not sure what is going on please help!

Thank you.

Was it helpful?

Solution

The concept here is that there is a distinct difference between selecting documents and selecting elements of a sub-document array. So what is happening currently in your query is exactly what should be happening. As the document contains at least one sub-document entry that matches your condition, then that document is found.

In order to "filter" the content of the sub-documents itself for more than one match, then you need to apply the .aggregate() method. And since you are expecting a count then this is what you want:

db.banners.aggregate([

    // Matching documents still makes sense
    { "$match": {
        "impression_details.date":{ 
            "$gte": ISODate("2014-04-24T00:00:00.000Z"), 
            "$lte": ISODate("2014-04-24T23:59:59.000Z")
        }
    }},

    // Unwind the array
    { "$unwind": "$impression_details" },

    // Actuall filter the array contents
    { "$match": {
        "impression_details.date":{ 
            "$gte": ISODate("2014-04-24T00:00:00.000Z"), 
            "$lte": ISODate("2014-04-24T23:59:59.000Z")
        }
    }},

    // Group back to the normal document form and get a count
    { "$group": {
        "_id": "$_id",
        "impression_details": { "$push": "$impression_details" },
        "count": { "$sum": 1 }
    }}

])

And that will give you a form that only has the elements that match your query in the array, as well as providing the count of those entries that were matched.

OTHER TIPS

Use the $elemMatch operator would do what you want.
In your query it meas to find all the documents whose impression_details field contains a data between ISODate("2014-04-24T00:00:00.000Z") and ISODate("2014-04-24T23:59:59.000Z"). The point is, it will return the whole document which is not what you want. So if you want only the subdocuments that satisfies your condition:

var docs = db.banners.find({
    "impression_details": {
        $elemMatch: {
            data: {
                $gte: ISODate("2014-04-24T00:00:00.000Z"),
                $lte: ISODate("2014-04-24T23:59:59.000Z")
            }
        }
    }
});
var count = 0;
docs.forEach(function(doc) {
    count += doc.impression_details.length;
});
print(count);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top