Reputation: 7623
I have the following MongoDB collection of documents, each containing a field called "history", which contains an array of sub-documents with fields "date" and "points".
[{
history: [{
date: "2019-20-20",
points: 1,
}, {
date: "2019-20-21",
points: 1,
}, {
date: "2019-20-22",
points: 1,
}, {
date: "2019-20-23",
points: 1,
}],
}, {
history: [{
date: "2019-20-20",
points: 1,
}, {
date: "2019-20-21",
points: 2,
}, {
date: "2019-20-22",
points: 3,
}, {
date: "2019-20-23",
points: 4,
}],
}]
I'm not sure what is the best way to construct a query that produces the below output. For the following example, the date range (inclusive) is "2019-20-21" to "2019-20-22". "totalPoints" is a new field, which contains the sum of all the points in the "history" field across that date range.
[{
history: [{
date: "2019-20-20",
points: 1,
}, {
date: "2019-20-21",
points: 1,
}, {
date: "2019-20-22",
points: 1,
}, {
date: "2019-20-23",
points: 1,
}],
totalPoints: 2,
}, {
history: [{
date: "2019-20-20",
points: 1,
}, {
date: "2019-20-21",
points: 2,
}, {
date: "2019-20-22",
points: 3,
}, {
date: "2019-20-23",
points: 4,
}],
totalPoints: 5,
}]
Below is a general idea of what I'm trying to do:
User.aggregate([{
$addFields: {
totalPoints: { $sum: points in "history" field if date range between "2019-20-21" and "2019-20-22" } ,
}
}]);
The reason I want to create a new "totalPoints" field is because eventually I want to sort via the "totalPoints" field.
Upvotes: 2
Views: 1636
Reputation: 103365
For a single pipeline, you can combine $reduce
with $filter
to get the sum as follows:
var startDate = "2019-20-21";
var endDate = "2019-20-22";
User.aggregate([
{ "$addFields": {
"totalPoints": {
"$reduce": {
"input": {
"$filter": {
"input": "$history",
"as": "el",
"cond": {
"$and": [
{ "$gte": ["$$el.date", startDate] },
{ "$lte": ["$$el.date", endDate ] },
]
}
}
},
"initialValue": 0,
"in": { "$add": [ "$$value", "$$this.points" ] }
}
}
} }
]);
Another alternative is having two pipeline stages where you start your aggregation with a filtered array which contains only the elements that match the date range query. Combine $addFields
with $filter
for this and your filter condition uses the conditional operator $and
with the comparison operators $gte
and $lte
. The following pipeline shows this:
{ "$addFields": {
"totalPoints": {
"$filter": {
"input": "$history",
"cond": {
"$and": [
{ "$gte": ["$$this.date", "2019-20-21"] },
{ "$lte": ["$$this.date", "2019-20-22"] },
]
}
}
}
} },
On getting the filtered array you can then get the sum easily in the next pipeline with $sum
, so your complete pipeline becomes
var startDate = "2019-20-21";
var endDate = "2019-20-22";
User.aggregate([
{ "$addFields": {
"totalPoints": {
"$filter": {
"input": "$history",
"cond": {
"$and": [
{ "$gte": ["$$this.date", startDate] },
{ "$lte": ["$$this.date", endDate ] },
]
}
}
}
} },
{ "$addFields": {
"totalPoints": { "$sum": "$totalPoints.points" }
} }
])
Upvotes: 2