问题
I have a mongo/mongoose schema which when queried retruns documents such as
{ "_id" : ObjectId("5907a5850b459d4fdcdf49ac"), "amount" : -33.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-04-26T23:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.581Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49ba"), "amount" : -61.3, "name" : "Amazon", "method" : "VIS", "date" : ISODate("2017-03-23T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.592Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49ce"), "amount" : -3.3, "name" : "Tesco", "method" : "VIS", "date" : ISODate("2017-03-15T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.601Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49cc"), "amount" : -26.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-16T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.600Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49f7"), "amount" : -63.3, "name" : "Sky", "method" : "VIS", "date" : ISODate("2017-03-02T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.617Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49be"), "amount" : -3.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-22T00:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.593Z"), "category" : "Not Set", "__v" : 0 }
I would like to write a query what provides the yearly, monthly and weekly spend for each of the vendors ("name" : "Amazon"
), so for example for vendor RINGGO:
- There are three spends in 2017 33.3+26.3+3.3 so total yearly spend would be 59.9
- There are two spends in the month 2017-03 with a sum of 26.3+3.3 so the monthly total would be 26.6
- Each of the spends are in different weeks so the weekly totals would be (for example) wk12 26.3, wk13 3.3, wk 15 33.3
I can write a query such as
db.statements.aggregate(
[
{ $group : { _id : "$name", amount: { $push: "$amount" } } }
]
)
which will aggregate all spends (amount
) by vendor name, but I'm not sure how to break this down by year, month, week as described above.
Edit in response to comment I'm not exactly sure that shape the result may have but ideally it would be something like the below:
I need the year, month, week etc so that the queries can be driven by url (e.g. domain.com/vendorname/2017
, domain.com/vendorname/2017/3
, domain.com/vendorname/2017/3/12
)
I would also like both the individual spends and the total spends for each year/month/week as I would like to print these to the page.
{ "_id" :
{ "year" : 2017,
"month" : 3,
"week" : 12 },
"name": "RINGGO", //vendor name
"YearlySpends":[ 33.3, 26.3, 3.3]
"totalYearlylyAmount" : [ 59.9]
"MonthlySpends":[ 26.3, 3.3]
"totalMonthlyAmount" : [ 26.6]
"WeeklylySpends":[ 3.3]
"totalWeeklylyAmount" : [3.3]
}
回答1:
A good approach would be to break the aggregate pipeline into several steps with the aim of calculating the aggregates with each group i.e. yearly, monthly and weekly aggregates.
I've made a feeble attempt at generating the said pipeline but not sure if that's what you are after but could give you some leads to a solution, better yet an optimal one. Perhaps someone else could give a better answer.
Consider the following untested pipeline:
db.statements.aggregate([
{
"$group": {
"_id": {
"name": "$name",
"year": { "$year": "$date" },
"month": { "$month": "$date" },
"week": { "$week": "$date" }
},
"total": { "$sum": "$amount" }
}
},
{
"$group": {
"_id": {
"name": "$_id.name",
"year": "$_id.year"
},
"YearlySpends": { "$push": "$total" },
"totalYearlyAmount": { "$sum": "$total" },
"data": { "$push": "$$ROOT" }
}
},
{ "$unwind": "$data" },
{
"$group": {
"_id": {
"name": "$_id.name",
"month": "$data._id.month"
},
"YearlySpends": { "$first": "$YearlySpends" },
"totalYearlyAmount": { "$first": "$totalYearlyAmount" },
"MonthlySpends": { "$push": "$data.total" },
"totalMonthlyAmount": { "$sum": "$data.total" },
"data": { "$push": "$data" }
}
},
{ "$unwind": "$data" },
{
"$group": {
"_id": {
"name": "$_id.name",
"week": "$data._id.week"
},
"YearlySpends": { "$first": "$YearlySpends" },
"totalYearlyAmount": { "$first": "$totalYearlyAmount" },
"MonthlySpends": { "$first": "$MonthlySpends" },
"totalMonthlyAmount": { "$first": "$totalMonthlyAmount" },
"WeeklySpends": { "$push": "$data.total" },
"totalWeeklyAmount": { "$sum": "$data.total" },
"data": { "$push": "$data" }
}
},
{ "$unwind": "$data" },
{
"$group": {
"_id": "$data._id",
"YearlySpends": { "$first": "$YearlySpends" },
"totalYearlyAmount": { "$first": "$totalYearlyAmount" },
"MonthlySpends": { "$first": "$MonthlySpends" },
"totalMonthlyAmount": { "$first": "$totalMonthlyAmount" },
"WeeklySpends": { "$first": "$WeeklySpends" },
"totalWeeklyAmount": { "$first": "$totalWeeklyAmount" }
}
}
])
Sample Output
/* 1 */
{
"_id" : {
"name" : "Tesco",
"year" : 2017,
"month" : 3,
"week" : 11
},
"YearlySpends" : [
-3.3
],
"totalYearlyAmount" : -3.3,
"MonthlySpends" : [
-3.3
],
"totalMonthlyAmount" : -3.3,
"WeeklySpends" : [
-3.3
],
"totalWeeklyAmount" : -3.3
}
/* 2 */
{
"_id" : {
"name" : "RINGGO",
"year" : 2017,
"month" : 4,
"week" : 17
},
"YearlySpends" : [
-3.3,
-26.3,
-33.3
],
"totalYearlyAmount" : -62.9,
"MonthlySpends" : [
-33.3
],
"totalMonthlyAmount" : -33.3,
"WeeklySpends" : [
-33.3
],
"totalWeeklyAmount" : -33.3
}
/* 3 */
{
"_id" : {
"name" : "RINGGO",
"year" : 2017,
"month" : 3,
"week" : 12
},
"YearlySpends" : [
-3.3,
-26.3,
-33.3
],
"totalYearlyAmount" : -62.9,
"MonthlySpends" : [
-3.3,
-26.3
],
"totalMonthlyAmount" : -29.6,
"WeeklySpends" : [
-3.3
],
"totalWeeklyAmount" : -3.3
}
/* 4 */
{
"_id" : {
"name" : "RINGGO",
"year" : 2017,
"month" : 3,
"week" : 11
},
"YearlySpends" : [
-3.3,
-26.3,
-33.3
],
"totalYearlyAmount" : -62.9,
"MonthlySpends" : [
-3.3,
-26.3
],
"totalMonthlyAmount" : -29.6,
"WeeklySpends" : [
-26.3
],
"totalWeeklyAmount" : -26.3
}
/* 5 */
{
"_id" : {
"name" : "Sky",
"year" : 2017,
"month" : 3,
"week" : 9
},
"YearlySpends" : [
-63.3
],
"totalYearlyAmount" : -63.3,
"MonthlySpends" : [
-63.3
],
"totalMonthlyAmount" : -63.3,
"WeeklySpends" : [
-63.3
],
"totalWeeklyAmount" : -63.3
}
/* 6 */
{
"_id" : {
"name" : "Amazon",
"year" : 2017,
"month" : 3,
"week" : 12
},
"YearlySpends" : [
-61.3
],
"totalYearlyAmount" : -61.3,
"MonthlySpends" : [
-61.3
],
"totalMonthlyAmount" : -61.3,
"WeeklySpends" : [
-61.3
],
"totalWeeklyAmount" : -61.3
}
UPDATE
If you wish to include filters within the aggregate operation then I would suggest you use the $match query as the first pipeline stage. However, if there is an initial $match step then the preceding steps would be altered slightly as you will be aggregating filtered results, very different from aggregating all the documents as a whole initially and then applying the filter on the results.
If you are to take the filter-first-then-aggregate route, consider running an aggregate operation that uses $match as the first step which filters the documents by vendor, then a preceding $redact pipeline step to further filter the documents on the month part of the date field and then the rest would be the $group stages:
Statements.aggregate([
{ "$match": { "name": req.params.vendor } },
{
"$redact": {
"$cond": [
{ "$eq": [{ "$month": "$date" }, parseInt(req.params.month) ]},
"$$KEEP",
"$$PRUNE"
]
}
},
.....
/*
add the remaining pipeline steps after
*/
], function(err, data){
if (err) throw err;
console.log(data);
})
If you are to take the group-first-then-filter route, then the filter would be after the last pipeline that gives the grouped result but applied on different fields as the documents down that part of the stream would be different from the original schema.
This route is not performant since you are beginning the aggregate operation with all the documents in the collection and then filtering afterwards:
Statements.aggregate([
.....
/*
place the initial pipeline steps from
the original query above here
*/
.....
{
"$match": {
"_id.name": req.params.vendor,
"_id.month": parseInt(req.params.month)
}
}
], function(err, data){
if (err) throw err;
console.log(data);
})
For multiple date filter parameters, the $redact operator would be
{
"$redact": {
"$cond": [
{
"$and": [
{ "$eq": [{ "$year": "$date" }, parseInt(req.params.year) ]},
{ "$eq": [{ "$month": "$date" }, parseInt(req.params.month) ]},
{ "$eq": [{ "$week": "$date" }, parseInt(req.params.week) ]}
]
},
"$$KEEP",
"$$PRUNE"
]
}
}
来源:https://stackoverflow.com/questions/43864813/mongo-mongoose-aggregating-by-date