MongoDB/Mongoose - Querying an array of objects by

2019-06-02 06:31发布

问题:

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.

回答1:

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]
                ]
            }
        }
    }
])


回答2:

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"
   }
}