问题
I have a query that is returning the total number of entries in a collection per year-month, grouped by a location. This is returning data exactly as I need it if the location has results for the year-month in question. However, is it possible to insert an entry for a month that does not have a result? For instance lets say if my $match has a date range of 01-2019 to 12-2019. I would like to have all 12 entries for the month with a default of total: 0. Is this possible
Truncated Schema :
{
branchId: { type: String, required: true },
orgId: { type: String, required: true },
stars: { type: Number, default: 0 },
reviewUpdatedAt: { type: Date, default: Date.now }
}
Example Query :
[
{
$match: {
stars: { $exists: true, $gte: 1 },
orgId: '100003'
reviewUpdatedAt: { $gte: new Date(fromDate), $lte: new Date(toDate) }
}
},
{
$group: {
_id: {
date: {
$dateToString: {
format: "%m-%Y",
date: "$reviewUpdatedAt"
}
},
loc: "$branchId"
},
total: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id.loc",
reviews: {
$push: {
total: "$total",
"date": "$_id.date"
}
}
}
}
]
回答1:
At first I thought this can be easily achieved through code, but even with MongoDB you can do that but with an input from code :
Let's say if your fromDate is June-2018 & toDate is June-2019, then by using your programming language you can easily get all months between those two dates in this format mm-yyyy. You can try to do this using MongoDB but I would rather prefer as an input to query.
Query :
db.collection.aggregate([
{
$group: {
_id: {
date: {
$dateToString: {
format: "%m-%Y",
date: "$reviewUpdatedAt"
}
},
loc: "$branchId"
},
Total: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id.loc",
reviews: {
$push: {
Total: "$Total",
"date": "$_id.date"
}
}
}
},
/** Overwrite existing reviews field with new array, So forming new array ::
* as you're passing all months between these dates get a difference of two arrays (input dates - existing dates after group)
* while will leave us with an array of missing dates, we would iterate on that missing dates array &
* concat actual reviews array with each missing date
* */
{
$addFields: {
reviews: {
$reduce: {
input: {
$setDifference: [
[
"06-2018",
"07-2018",
"08-2018",
"09-2018",
"10-2018",
"11-2018",
"12-2018",
"01-2019",
"02-2019",
"03-2019",
"04-2019",
"05-2019",
"06-2019"
],
"$reviews.date"
]
},
initialValue: "$reviews",
in: {
$concatArrays: [
"$$value",
[
{
date: "$$this",
Total: 0
}
]
]
}
}
}
}
}
])
Test : MongoDB-Playground
Ref : javascript-get-all-months-between-two-dates
回答2:
so step back and realize you seek a display of data that doesn't exist in the db...let's say there is no data for 3/19. this is not a mongo issue but universal for any db. one creates a 'time table' in your case perhaps it is month/year...and for mongo it is documents/collection...this provides framework data for each month for the initial match..to which one's join ($lookup in mongo) will have null for 3/19...
adding a time table is standard in analytic apps -some come with that feature embedded as part of their time based analytics feature so the database doesn't need to do anything.....but to do so via general query/reporting in mongo and sql databases one would need to manually add that time collection/table
来源:https://stackoverflow.com/questions/60571986/mongodb-aggregation-to-add-missing-months-between-two-dates-after-grouping-on-da