如何在 MongoDB 集合中分别对几个月的数据求和
How to sum several months of data separately in a MongoDB collection
我正在尝试对集合的子数组中的字段进行计数,我想每个月都这样做。我在 Mongo 2.6 中工作,但最近升级到 3.0.12 导致查询中出现一些错误结果。似乎总和没有为几个查询重置。
所以目前我正在异步执行十二个查询并等待它们全部完成。同样,这在 2.6 中有效。我的 table 结构是这样的:
{
"_id" : ObjectId("<id>"),
"userId" : ObjectId("<id>"),
"accountId" : "1234567890",
"transactionId" : "22222222333",
"date" : ISODate("2016-09-08T04:00:00.000Z"),
"description" : "SUPERCOOL STORE",
"amount" : -123.45,
"allocations" : [
{
"jarId" : ObjectId("566faf1937af9ae11ef84bc4"),
"amount" : -100
},
{
"jarId" : ObjectId("566faf1937af9ae11ef84bc1"),
"amount" : -23.45
}
],
"reconciled" : true,
"tally" : true,
"split" : true
}
这是我的代码:
var getTransactionAggregate = function (userId, month) {
var deferred = q.defer();
var nextMonth = moment(month).add(1, 'M');
Transactions.aggregate([
{$match: {
userId: userId,
tally: true,
date: {
$gte: month.toDate(),
$lt: nextMonth.toDate()
}
}},
{$project: { _id: 0, allocations: 1 } },
{$unwind: '$allocations'},
{$group: {_id: '$allocations.jarId', total: {$sum: '$allocations.amount'}}}
]).then(function(data) {
deferred.resolve(data);
})
.catch(function (err) {
logger.error(err);
deferred.reject(err);
});
return deferred.promise;
};
summaryReport = function (req, res) {
Jars.aggregate([
{ $match: {userId: new ObjectID(req.user._id)} },
{ $sort: {name: 1} }
])
.then(function (jars) {
var month = moment(moment().format('YYYY-MM') + '-01T00:00:00.000');
var i, j;
var promises = [];
for (i = 0; i < 12; i++) {
promises.push(getTransactionAggregate(new ObjectID(req.user._id), month));
month.add(-1, 'M');
}
q.allSettled(promises).then(function (data) {
for (i = 0; i < data.length; i++) {
// data[i].value here is returned incorrectly from the queries
........
});
});
};
所以基本上发生的事情是第一个月包含正确的数据,但总和似乎继续包含之前所有月份的数据。如果我分解查询,则会在日期范围内返回正确的交易,并且展开工作也正常。就在 groupBy 步骤似乎是罪魁祸首的时候。在我将 Mongo 升级到 3.0.12.
之前,相同的逻辑工作正常
是否有更好的方法一次性执行此查询,或者执行十二个查询是最好的方法吗?
好像是$match
阶段的问题。您的 date
字段有两个表达式,并且这种情况下您需要使用 $and
运算符,如 docs:
中指定
MongoDB provides an implicit AND operation when specifying a comma
separated list of expressions. Using an explicit AND with the $and
operator is necessary when the same field or operator has to be
specified in multiple expressions.
所以它变成了:
{$match: {
userId: userId,
tally: true,
$and: [
{ date: { $gte : month.toDate() } },
{ date: { $lt: nextMonth.toDate() } }
]
}}
它最终与比赛有关,尽管不是因为上面答案中提到的 $and 案例。它与日期匹配有关,我猜 object.toDate() 与使用 new Date() 时的日期对象 return 不同,尽管我认为它们是相同的。
无论如何,工作逻辑是这样的:
Transactions.aggregate([
{$match: {
userId: userId,
tally: true,
$and: [
{ date: { $gt : new Date(month.toISOString()) } },
{ date: { $lt: new Date(nextMonth.toISOString()) } }
]
}},
{$unwind: '$allocations'},
{$group: {_id: '$allocations.jarId', total: {$sum: '$allocations.amount'}}}
])
感谢 Date query with ISODate in mongodb doesn't seem to work 和@Leif 为我指明了正确的方向。
我正在尝试对集合的子数组中的字段进行计数,我想每个月都这样做。我在 Mongo 2.6 中工作,但最近升级到 3.0.12 导致查询中出现一些错误结果。似乎总和没有为几个查询重置。
所以目前我正在异步执行十二个查询并等待它们全部完成。同样,这在 2.6 中有效。我的 table 结构是这样的:
{
"_id" : ObjectId("<id>"),
"userId" : ObjectId("<id>"),
"accountId" : "1234567890",
"transactionId" : "22222222333",
"date" : ISODate("2016-09-08T04:00:00.000Z"),
"description" : "SUPERCOOL STORE",
"amount" : -123.45,
"allocations" : [
{
"jarId" : ObjectId("566faf1937af9ae11ef84bc4"),
"amount" : -100
},
{
"jarId" : ObjectId("566faf1937af9ae11ef84bc1"),
"amount" : -23.45
}
],
"reconciled" : true,
"tally" : true,
"split" : true
}
这是我的代码:
var getTransactionAggregate = function (userId, month) {
var deferred = q.defer();
var nextMonth = moment(month).add(1, 'M');
Transactions.aggregate([
{$match: {
userId: userId,
tally: true,
date: {
$gte: month.toDate(),
$lt: nextMonth.toDate()
}
}},
{$project: { _id: 0, allocations: 1 } },
{$unwind: '$allocations'},
{$group: {_id: '$allocations.jarId', total: {$sum: '$allocations.amount'}}}
]).then(function(data) {
deferred.resolve(data);
})
.catch(function (err) {
logger.error(err);
deferred.reject(err);
});
return deferred.promise;
};
summaryReport = function (req, res) {
Jars.aggregate([
{ $match: {userId: new ObjectID(req.user._id)} },
{ $sort: {name: 1} }
])
.then(function (jars) {
var month = moment(moment().format('YYYY-MM') + '-01T00:00:00.000');
var i, j;
var promises = [];
for (i = 0; i < 12; i++) {
promises.push(getTransactionAggregate(new ObjectID(req.user._id), month));
month.add(-1, 'M');
}
q.allSettled(promises).then(function (data) {
for (i = 0; i < data.length; i++) {
// data[i].value here is returned incorrectly from the queries
........
});
});
};
所以基本上发生的事情是第一个月包含正确的数据,但总和似乎继续包含之前所有月份的数据。如果我分解查询,则会在日期范围内返回正确的交易,并且展开工作也正常。就在 groupBy 步骤似乎是罪魁祸首的时候。在我将 Mongo 升级到 3.0.12.
之前,相同的逻辑工作正常是否有更好的方法一次性执行此查询,或者执行十二个查询是最好的方法吗?
好像是$match
阶段的问题。您的 date
字段有两个表达式,并且这种情况下您需要使用 $and
运算符,如 docs:
MongoDB provides an implicit AND operation when specifying a comma separated list of expressions. Using an explicit AND with the $and operator is necessary when the same field or operator has to be specified in multiple expressions.
所以它变成了:
{$match: {
userId: userId,
tally: true,
$and: [
{ date: { $gte : month.toDate() } },
{ date: { $lt: nextMonth.toDate() } }
]
}}
它最终与比赛有关,尽管不是因为上面答案中提到的 $and 案例。它与日期匹配有关,我猜 object.toDate() 与使用 new Date() 时的日期对象 return 不同,尽管我认为它们是相同的。
无论如何,工作逻辑是这样的:
Transactions.aggregate([
{$match: {
userId: userId,
tally: true,
$and: [
{ date: { $gt : new Date(month.toISOString()) } },
{ date: { $lt: new Date(nextMonth.toISOString()) } }
]
}},
{$unwind: '$allocations'},
{$group: {_id: '$allocations.jarId', total: {$sum: '$allocations.amount'}}}
])
感谢 Date query with ISODate in mongodb doesn't seem to work 和@Leif 为我指明了正确的方向。