[英]Group records by month and count them - Mongoose, nodeJs, mongoDb
我需要在數據庫(貓鼬)中查詢並返回一年中每個月為一種特定產品(一年內)所做的銷售數量。
我是 node 和 mongoDb 的新手,我提供了一個“虛擬”解決方案,我在數據庫中查詢並取回一個產品的所有結果,然后我使用 3 個循環在幾個月內拆分結果,但我認為它使用了更多資源比它應該使用的更多,如果它充滿更多數據,它會使用更多,所以我需要幫助進行數據庫查詢來解決這個問題。
這是我的代碼的一部分:
假設從17-02-2020到17-02-2019需要結果,我知道如果是從1 月到12 月,它將 go 進入一個循環,但我有另一部分代碼控制它是否需要 1年份結果例如: 01-01-2020到31-12-2020它不會執行下面的代碼,我正在談論的代碼只有一個循環,哈哈。
let startTime = performance.now();
Sales.find({productId:req.params.productId, "created_at": { "$gte": oneYearFromNow, "$lte": dateNow}}).then(result => {
let newMonthsArray= new Array();
let monthsArray = ['January','February','March','April','May','June','July','August','September','October', 'November','December'];
let months = {};
for(let i=parseInt(req.params.startDate.substring(5,7))-1; i<12; i++){
let year = parseInt(req.params.startDate.substring(0,4))-1;
let month = parseInt(req.params.startDate.substring(5,7));
newMonth = monthsArray[i] + '-' + year;
newMonthsArray.push(newMonth);
months[newMonth] = 0;
}
for(let i=0; i<parseInt(req.params.startDate.substring(5,7)); i++){
let year = parseInt(req.params.startDate.substring(0,4));
let month = parseInt(req.params.startDate.substring(5,7));
newMonth = monthsArray[i] + '-' + year;
newMonthsArray.push(newMonth);
months[newMonth] = 0;
}
for(i=0; i<result.length; i++){
let getDate = result[i].created_at.toISOString();
let year = getDate.substring(0,4);
let month = parseInt(getDate.substring(5,7));
let monthName = monthsArray[month-1];
let date = monthName + '-' + year;
let count = Number(months[date]) + 1;
months[date] = count;
}
let endTime = performance.now();
res.status(200).send({Data: months, 'Execution time': endTime - startTime + ' mls'});
});
我希望一切都清楚,我想我需要使用聚合,但我不知道如何!
樣本數據:
{
{
"created_at": "2020-04-04T17:02:07.832Z",
"updated_at": "2020-04-04T17:02:07.832Z",
"_id": "5e88bdcda3080736ac70f9c1",
"price": 16800,
"productId": "5e88bf90b9e5102ae46b154e",
"__v": 0
},
{
"created_at": "2020-04-04T17:02:07.832Z",
"updated_at": "2020-04-04T17:02:07.832Z",
"_id": "5e88bdf9a3080736ac70f9c2",
"price": 12800,
"productId": "5e88bf90b9e5102ae46b154e",
"__v": 0
}
}
期望的結果:
這是一個聚合查詢,它返回預期的 output。 一些示例文件:
[
{ created_at: "2020-04-04T17:02:07.832Z", productId: 1 },
{ created_at: "2020-02-01T17:02:07.832Z", productId: 1 },
{ created_at: "2020-02-19T17:02:07.832Z", productId: 1 },
{ created_at: "2019-05-22T17:02:07.832Z", productId: 1 },
{ created_at: "2020-01-15T17:02:07.832Z", productId: 1 },
{ created_at: "2020-01-30T17:02:07.832Z", productId: 2 }, // not selected
{ created_at: "2019-03-15T17:02:07.832Z", productId: 1 } // not selected
]
輸入變量和聚合:
let TODAY = "2020-04-06T23:59:59"
let YEAR_BEFORE = "2019-04-07T00:00:00"
let req = { params: { productId: 1 } }
const monthsArray = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]
db.sales.aggregate( [
{
$match: {
productId: req.params.productId,
created_at: { $gte: YEAR_BEFORE, $lte: TODAY }
}
},
{
$group: {
_id: { "year_month": { $substrCP: [ "$created_at", 0, 7 ] } },
count: { $sum: 1 }
}
},
{
$sort: { "_id.year_month": 1 }
},
{
$project: {
_id: 0,
count: 1,
month_year: {
$concat: [
{ $arrayElemAt: [ monthsArray, { $subtract: [ { $toInt: { $substrCP: [ "$_id.year_month", 5, 2 ] } }, 1 ] } ] },
"-",
{ $substrCP: [ "$_id.year_month", 0, 4 ] }
]
}
}
},
{
$group: {
_id: null,
data: { $push: { k: "$month_year", v: "$count" } }
}
},
{
$project: {
data: { $arrayToObject: "$data" },
_id: 0
}
}
] )
output:
{
"data" : {
"May-2019" : 1,
"January-2020" : 1,
"February-2020" : 2,
"April-2020" : 1
}
}
這是更新的聚合。
請注意以下更改:(1) 新常量 FIRST_MONTH 和 LAST_MONTH,(2) 將monthsArray
變量名稱更改為 MONTHS_ARRAY,(3) 添加了 3 個新的管道階段。
前兩個管道階段(新)構建了一個包含所有月份的模板(涵蓋從輸入日期范圍和到輸入日期范圍)。 第三個新階段將模板與從先前聚合中派生的 output 數據合並。
const FIRST_MONTH = 1
const LAST_MONTH = 12
const MONTHS_ARRAY = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]
let TODAY = "2020-04-06T23:59:59"
let YEAR_BEFORE = "2019-04-07T00:00:00"
db.sales.aggregate( [
{
$match: {
productId: req.params.productId,
created_at: { $gte: YEAR_BEFORE, $lte: TODAY }
}
},
{
$group: {
_id: { "year_month": { $substrCP: [ "$created_at", 0, 7 ] } },
count: { $sum: 1 }
}
},
{
$sort: { "_id.year_month": 1 }
},
{
$project: {
_id: 0,
count: 1,
month_year: {
$concat: [
{ $arrayElemAt: [ monthsArray, { $subtract: [ { $toInt: { $substrCP: [ "$_id.year_month", 5, 2 ] } }, 1 ] } ] },
"-",
{ $substrCP: [ "$_id.year_month", 0, 4 ] }
]
}
}
},
{
$group: {
_id: null,
data: { $push: { k: "$month_year", v: "$count" } }
}
},
{
$addFields: {
start_year: { $substrCP: [ YEAR_BEFORE, 0, 4 ] },
end_year: { $substrCP: [ TODAY, 0, 4 ] },
months1: { $range: [ { $toInt: { $substrCP: [ YEAR_BEFORE, 5, 2 ] } }, { $add: [ LAST_MONTH, 1 ] } ] },
months2: { $range: [ FIRST_MONTH, { $add: [ { $toInt: { $substrCP: [ TODAY, 5, 2 ] } }, 1 ] } ] }
}
},
{
$addFields: {
template_data: {
$concatArrays: [
{ $map: {
input: "$months1", as: "m1",
in: {
count: 0,
month_year: {
$concat: [ { $arrayElemAt: [ MONTHS_ARRAY, { $subtract: [ "$$m1", 1 ] } ] }, "-", "$start_year" ]
}
}
} },
{ $map: {
input: "$months2", as: "m2",
in: {
count: 0,
month_year: {
$concat: [ { $arrayElemAt: [ MONTHS_ARRAY, { $subtract: [ "$$m2", 1 ] } ] }, "-", "$end_year" ]
}
}
} }
]
}
}
},
{
$addFields: {
data: {
$map: {
input: "$template_data", as: "t",
in: {
k: "$$t.month_year",
v: {
$reduce: {
input: "$data", initialValue: 0,
in: {
$cond: [ { $eq: [ "$$t.month_year", "$$this.k"] },
{ $add: [ "$$this.v", "$$value" ] },
{ $add: [ 0, "$$value" ] }
]
}
}
}
}
}
}
}
},
{
$project: {
data: { $arrayToObject: "$data" },
_id: 0
}
}
] )
output:
{
"data" : {
"April-2019" : 0,
"May-2019" : 1,
"June-2019" : 0,
"July-2019" : 0,
"August-2019" : 0,
"September-2019" : 0,
"October-2019" : 0,
"November-2019" : 0,
"December-2019" : 0,
"January-2020" : 1,
"February-2020" : 2,
"March-2020" : 0,
"April-2020" : 1
}
}
是的,你是對的,你需要使用聚合。 這應該有效:
// NOTE: It is important that the the month strings start from the second element in this array
// becuase the $month operator returns month values as numbers from 1 to 12.
const monthStrings = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
Sales.aggregate([
{
$match: {
// Match only salses with a specific productId
productId: req.params.productId,
// Match only salses that fufils the date constraint below
$expr: {
$and: [
{ $gt: ["$created_at", oneYearFromNow] },
{ $lt: ["$created_at", dateNow] }
],
}
}
},
{
$group: {
// Group by both month and year of the sale
_id: {
month: { $month: "$created_at" },
year: { $year: "$created_at" },
},
// Count the no of sales
count: {
$sum: 1
}
}
},
// Adding a project here to just to format the group date better
{
$project: {
_id: {
$concat: [
{
$arrayElemAt: [
monthStrings,
"$_id.month"
]
},
"-",
"$_id.year"
]
},
count: 1,
}
}
])
您可以在這個Playground中測試聚合管道。
output 將是這樣的對象數組:
{ "_id": *, "count": * }
其中 _id 的值是格式為<month>-<year>
的字符串(例如April-2019
),表示銷售月份和年份。 計數值是該月/年發生的銷售數量。
我在聚合管道中使用了 $match、$group 和 $project 階段的組合,您可以分別在此處、 此處和此處找到有關這些階段如何工作的更多詳細信息。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.