4

I have the following metrics collection:

{
    name: "Hello",
    values: [
        {
            value: 2629,
            date: "2016-10-28T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdee8"
        },
        {
            value: 1568,
            date: "2016-10-29T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdee7"
        },
        {
            value: 1547,
            date: "2016-10-30T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdee6"
        },
        {
            value: 1497,
            date: "2016-10-31T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdee5"
        },
        {
            value: 3031,
            date: "2016-11-01T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdee4"
        },
        {
            value: 2559,
            date: "2016-11-02T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdee3"
        },
        {
            value: 2341,
            date: "2016-11-03T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdee2"
        },
        {
            value: 2188,
            date: "2016-11-04T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdee1"
        },
        {
            value: 3280,
            date: "2016-11-05T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdee0"
        },
        {
            value: 4638,
            date: "2016-11-06T07:00:00.000Z",
            _id: "58453abfef7aaa15ac1fdedf"
        }
    ]
},
.... more of the same

What I would like to get is all the values between a custom date range. I've tried the following query but I still get the entire values array returned:

{
    name: "Hello", 
    values: {
        $elemMatch: {
            date: {
                $lt: "2016-11-03T07:00:00.000Z", 
                $gt: "2016-10-28T07:00:00.000Z" 
            }
        }
    }
}

Maybe I saved my dates in a wrong format ? Any help would be greatly appreciated.

2 Answers 2

6

You can run an aggregation pipeline that uses the $filter operator on the values array. The following mongo shell query demonstrates this:

var start = new Date("2016-10-28T07:00:00.000Z"),
    end = new Date("2016-11-03T07:00:00.000Z");

db.metrics.aggregate([
    { 
        "$match": { 
            "name": "Hello",
            "values.date": { "$gt": start, "$lt": end }
        } 
    },
    {
        "$project": {
            "name": 1,
            "values": {
                "$filter": {
                    "input": "$values",
                    "as": "value",
                    "cond": { 
                        "$and": [
                            { "$gt": [ "$$value.date", start ] },
                            { "$lt": [ "$$value.date", end ] }
                        ]
                    }
                }
            }
        }
    }
])

Sample Output

/* 1 */
{
    "_id" : ObjectId("5845453145fda1298fa50db9"),
    "name" : "Hello",
    "values" : [ 
        {
            "value" : 1568,
            "date" : ISODate("2016-10-29T07:00:00.000Z"),
            "_id" : ObjectId("58453abfef7aaa15ac1fdee7")
        }, 
        {
            "value" : 1547,
            "date" : ISODate("2016-10-30T07:00:00.000Z"),
            "_id" : ObjectId("58453abfef7aaa15ac1fdee6")
        }, 
        {
            "value" : 1497,
            "date" : ISODate("2016-10-31T07:00:00.000Z"),
            "_id" : ObjectId("58453abfef7aaa15ac1fdee5")
        }, 
        {
            "value" : 3031,
            "date" : ISODate("2016-11-01T07:00:00.000Z"),
            "_id" : ObjectId("58453abfef7aaa15ac1fdee4")
        }, 
        {
            "value" : 2559,
            "date" : ISODate("2016-11-02T07:00:00.000Z"),
            "_id" : ObjectId("58453abfef7aaa15ac1fdee3")
        }
    ]
}

For MongoDB 3.0, the following workaround applies:

var start = new Date("2016-10-28T07:00:00.000Z"),
    end = new Date("2016-11-03T07:00:00.000Z");

db.metrics.aggregate([
    { 
        "$match": { 
            "name": "Hello",
            "values.date": { "$gt": start, "$lt": end }
        } 
    },
    {
        "$project": {
            "name": 1,
            "values": {
                "$setDifference": [
                    {
                        "$map": {
                            "input": "$values",
                            "as": "value",
                            "in": {
                                "$cond": [
                                    { 
                                        "$and": [
                                            { "$gt": [ "$$value.date", start ] },
                                            { "$lt": [ "$$value.date", end ] }
                                        ]
                                    },
                                    "$$value",
                                    false
                                ]
                            }
                        }
                    },
                    [false]
                ]
            }
        }
    }
])
Sign up to request clarification or add additional context in comments.

5 Comments

Hi, I'm getting the following error: "errmsg" : "exception: invalid operator '$filter'", "code" : 15999, "ok" : 0
What is your MongoDB server version?
Current version: 3.0.12
In that case the above won't work unless you bump your version to 3.2. Let me update my answer with a workaround.
Wow that works. Thank you so much. I haven't been studying Mongo for long so things aren't still as clear as they should be. Really appreciate the help.
0

The Aggregation Framework in MongoDB 2.2+ provides an alternative to Map/Reduce. The $unwind operator can be used to separate your values array into a stream of documents that can be matched:

db.tmp.aggregate(
    // Start with a $match pipeline which can take advantage of an index and limit documents processed
    { $match : {
        name: "Hello",
        "values.date": {
            $lt: "2016-11-03T07:00:00.000Z",
            $gt: "2016-10-28T07:00:00.000Z" }
    }},
    { $unwind : "$values" },
    { $match : {
        name: "Hello",
        "values.date": {
            $lt: "2016-11-03T07:00:00.000Z",
            $gt: "2016-10-28T07:00:00.000Z" }
    }}
)

Sample output:

{
"_id":ObjectId("5845432720ce37bdc7e9ca1c"),
   "name":"Hello",
   "values":{  
      "value":1568,
      "date":"2016-10-29T07:00:00.000Z",
      "_id":"58453abfef7aaa15ac1fdee7"
   }
},{  
   "_id":ObjectId("5845432720ce37bdc7e9ca1c"),
   "name":"Hello",
   "values":{  
      "value":1547,
      "date":"2016-10-30T07:00:00.000Z",
      "_id":"58453abfef7aaa15ac1fdee6"
   }
},{  
   "_id":ObjectId("5845432720ce37bdc7e9ca1c"),
   "name":"Hello",
   "values":{  
      "value":1497,
      "date":"2016-10-31T07:00:00.000Z",
      "_id":"58453abfef7aaa15ac1fdee5"
   }
},{  
   "_id":ObjectId("5845432720ce37bdc7e9ca1c"),
   "name":"Hello",
   "values":{  
      "value":3031,
      "date":"2016-11-01T07:00:00.000Z",
      "_id":"58453abfef7aaa15ac1fdee4"
   }
},{  
   "_id":ObjectId("5845432720ce37bdc7e9ca1c"),
   "name":"Hello",
   "values":{  
      "value":2559,
      "date":"2016-11-02T07:00:00.000Z",
      "_id":"58453abfef7aaa15ac1fdee3"
   }
}

3 Comments

Hi. That's just returning one value now. I'd like to get all the values between 2016-10-28 and 2016-11-03.
check it again!
it now returns just an empty array.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.