Агрегация Mongo / Mongoose - $redact и $ cond выпуски
Мне посчастливилось получить удивительный ответ на другой вопрос SO Mongo / Mongoose - Aggregating by Date от @chridam, который дал набор документов, таких как:
{ "_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 }
Требовался запрос, который суммировал бы расходы по поставщикам, годам, месяцам и неделям. Запрос приведен ниже, и он почти фантастически работает, но поскольку я использовал его в своем приложении, я заметил значительную проблему
db.statements.aggregate([
{ "$match": { "name": "RINGGO" } },
{
"$redact": {
"$cond": [
{
"$and": [
{ "$eq": [{ "$year": "$date" }, 2017 ]}, // within my route this uses parseInt(req.params.year)
{ "$eq": [{ "$month": "$date" }, 3 ]}, // within my route this uses parseInt(req.params.month)
{ "$eq": [{ "$week": "$date" }, 12 ]} // within my route this uses parseInt(req.params.week)
]
},
"$$KEEP",
"$$PRUNE"
]
}
},{
"$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" }
}
}
])
Выполнение этого запроса возвращает
{ "_id" :
{ "name" : "RINGGO",
"year" : 2017,
"month" : 3,
"week" : 12 },
"YearlySpends" : [ -9.6 ],
"totalYearlyAmount" : -9.6,
"MonthlySpends" : [ -9.6 ],
"totalMonthlyAmount" : -9.6,
"WeeklySpends" : [ -9.6 ],
"totalWeeklyAmount" : -9.6
}
И когда я измените на желание видеть расходы за месяц
"$cond": [
{
"$and": [
{ "$eq": [{ "$year": "$date" }, 2017 ]},
{ "$eq": [{ "$month": "$date" }, 3 ]}
]
},
"$$KEEP",
"$$PRUNE"
]
Я получаю:
{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 12 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -9.6 ], "totalWeeklyAmount" : -9.6 }
{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 9 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -3.3 ], "totalWeeklyAmount" : -3.3 }
{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 11 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -9.6 ], "totalWeeklyAmount" : -9.6 }
{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 13 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -3.3 ], "totalWeeklyAmount" : -3.3 }
Однако, когда я запускаю простой db.statements.find({"name":"RINGGO"}), я получаю:
{ "_id" : ObjectId("5907a5850b459d4fdcdf49ac"), "amount" : -3.3, "name" : "RINGGO", "method" : "VIS", "date" : ISODate("2017-03-26T23:00:00Z"), "importDate" : ISODate("2017-05-01T21:15:49.581Z"), "category" : "Not Set", "__v" : 0 }
{ "_id" : ObjectId("5907a5850b459d4fdcdf49ba"), "amount" : -6.3, "name" : "RINGGO", "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" : "RINGGO", "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" : -6.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" : -3.3, "name" : "RINGGO", "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 }
Таким образом, вы можете видеть, что существует другое количество элементов в MonthlySpends в предыдущем выводе по сравнению с тем, что показано в выводе из find by name. Кроме того, вы можете видеть, что некоторые значения суммируются вместе в MonthlySpends, когда они не должны быть.
В идеале я хочу получить выход, который:
когда у меня есть $redact содержащий:
"$cond": [
{
"$and": [
{ "$eq": [{ "$year": "$date" }, 2017 ]},
{ "$eq": [{ "$month": "$date" }, 3 ]},
{ "$eq": [{ "$week": "$date" }, 12 ]}
]
},
"$$KEEP",
"$$PRUNE"
]
Возвращает
{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3, "week" : 12 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997, "WeeklySpends" : [ -9.6 ], "totalWeeklyAmount" : -9.6 }
Когда у меня есть $redact содержащий:
"$cond": [
{
"$and": [
{ "$eq": [{ "$year": "$date" }, 2017 ]},
{ "$eq": [{ "$month": "$date" }, 3 ]},
]
},
"$$KEEP",
"$$PRUNE"
]
Возвращает
{ "_id" : { "name" : "RINGGO", "year" : 2017, "month" : 3 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997, "MonthlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalMonthlyAmount" : -25.799999999999997 }
Когда у меня есть $redact содержащий:
"$cond": [
{
"$and": [
{ "$eq": [{ "$year": "$date" }, 2017 ]}
]
},
"$$KEEP",
"$$PRUNE"
]
Возвращает
{ "_id" : { "name" : "RINGGO", "year" : 2017 }, "YearlySpends" : [ -3.3, -9.6, -9.6, -3.3 ], "totalYearlyAmount" : -25.799999999999997}
Любая помощь в этом очень нужна. Я пробовал возиться с запросом, но боюсь, что я просто не понимаю его достаточно, чтобы изменить его правильно.
Моя версия Мангуста-это
^4.9.5, а мой монго-это 3.4.2. 1 ответ:
Вы можете попробовать
$facetс$addFieldsдля параллельной агрегации в версии3.4.Это снизит общую сложность, и вы сможете одновременно выполнять группировки с собственным соответствующим входом.
Приведенный ниже код динамически строит конвейер агрегации на основе объекта запроса.
// Sample request var request = { "name":"RINGGO", "year": 2017, "month":3, "week":12 }; // Build initial match document on name var match1 = { name: request["name"] }; // Build project & facet document for date based aggregation var addFields = {}; var facet = {}; // Add year followed by year facet if (request["year"]) { addFields["year"] = { "$year": "$date" }, facet["Yearly"] = [ { "$match":{ "year": request["year"] } }, { "$group": { "_id": { "name": "$name", "year": "$year" }, "spend": { "$push":"$amount" }, "total": { "$sum": "$amount" } } } ]; } // Add month followed by month facet if (request["month"]) { addFields["month"] = { "$month": "$date" }; facet["Monthly"] = [ { "$match":{ "month": request["month"] } }, { "$group": { "_id": { "name": "$name", "month": "$month" }, "spend": { "$push":"$amount" }, "total": { "$sum": "$amount" } } } ]; } // Add week followed by week facet if (request["week"]) { addFields["week"] = { "$week": "$date" }; facet["Weekly"] = [ { "$match":{ "week": request["week"] } }, { "$group": { "_id": { "name": "$name", "week": "$week" }, "spend": { "$push":"$amount" }, "total": { "$sum": "$amount" } } } ]; } // Use aggregate builder statements.aggregate() .match(match1) .append({"$addFields": addFields}) // No addFields stage in mongoose builder .facet(facet) .exec(function(err, data) {});Запрос оболочки Mongo для критериев
name/year/month/week.db.statements.aggregate({ '$match': { name: 'RINGGO' } }, { '$addFields': { year: { '$year': '$date' }, month: { '$month': '$date' }, week: { '$week': '$date' } } }, { '$facet': { Yearly: [{ '$match': { year: 2017 } }, { '$group': { _id: { name: '$name', year: '$year' }, spend: { '$push': '$amount' }, total: { '$sum': '$amount' } } } ], Monthly: [{ '$match': { month: 3 } }, { '$group': { _id: { name: '$name', month: '$month' }, spend: { '$push': '$amount' }, total: { '$sum': '$amount' } } } ], Weekly: [{ '$match': { week: 12 } }, { '$group': { _id: { name: '$name', week: '$week' }, spend: { '$push': '$amount' }, total: { '$sum': '$amount' } } } ] } })Пример Ответа
{ "Yearly": [{ "_id": { "name": "RINGGO", "year": 2017 }, "spend": [-3.3, -6.3, -3.3, -6.3, -3.3, -3.3], "total": -25.799999999999997 }], "Monthly": [{ "_id": { "name": "RINGGO", "month": 3 }, "spend": [-3.3, -6.3, -3.3, -6.3, -3.3, -3.3], "total": -25.799999999999997 }], "Weekly": [{ "_id": { "name": "RINGGO", "week": 12 }, "spend": [-6.3, -3.3], "total": -9.6 }] }Аналогичную агрегацию можно выполнить для входных данных
Year/MonthиYear. ценности.Таким образом, вы можете видеть, что существует различное количество элементов в MonthlySpends в предыдущие выходные по сравнению с тем что показано в выходных данных от находки по имени. Кроме того, вы можете видеть, что некоторые из значений являются суммируются вместе в месячные сроки, когда их не должно быть.
Это происходит в
$group1, где$weekагрегация свертывает каждую из двух дат [15, 16] сумма в неделю 11 и другие две даты [22, 23] сумма в неделю 12 позже, чтобы показать, как суммируются итоги вMonthySpends.
Comments