MongoDB 聚合组最近 7 天的平均评分(空值)
MongoDB aggregation group average rating by last 7 days with empty values
我正在尝试查询集合并检索过去 7 天(不包括当天)每一天的平均值。在某些或所有的日子里,可能没有平均值。
这是我目前的情况:
var dateTill = moment({hour:0,minute:0}).subtract(1, 'days')._d
var dateSevenDaysAgo = moment({hour:0,minute:0}).subtract(7, 'days')._d;
Rating.aggregate([
{
$match:{
userTo:facebookId,
timestamp:{$gt:dateSevenDaysAgo,$lt:dateTill}
}
},
{
$group:{
_id:{day:{'$dayOfMonth':'$timestamp'}},
average:{$avg:'$rating'}
}
},
{
$sort:{
'_id.day':1
}
}
]
这给了我
[ { _id: { day: 20 }, average: 1 },
{ _id: { day: 22 }, average: 3 },
{ _id: { day: 24 }, average: 5 } ]
我想要得到的是这样的:
[1,,3,,5,,]
表示最近 7 天的平均值,并且有一个空元素,表示当天没有平均值。
我可以尝试创建一个函数来检测差距在哪里,但是当平均值分布在两个不同的月份时,这将不起作用。例如 (July 28,29,30,31,Aug 1,2] - 八月的日子将被排序到我想要的数组的前面。
有更简单的方法吗?
谢谢!
人们经常询问 "empty results",他们的想法通常来自他们如何通过 SQL 查询来解决问题。
但是虽然 "possible" 为不包含分组键的项目抛出一组 "empty results",但这是一个困难的过程,很像 SQL 方法使用,它只是人为地将这些值放入语句中,它确实不是一个非常受性能驱动的替代方案。想想 "join" 使用一组制造的密钥。效率不高。
更聪明的方法是直接在客户端 API 准备好这些结果,而不发送到服务器。然后聚合输出可以 "merged" 与这些结果创建一个完整的集合。
无论您想要存储要合并的集合取决于您,它只需要基本的 "hash table" 和查找。但这里有一个使用 nedb 的示例,它允许您保持 MongoDB 套查询和更新的思维方式:
var async = require('async'),
mongoose = require('mongoose'),
DataStore = require('nedb'),
Schema = mongoose.Schema,
db = new DataStore();
mongoose.connect('mongodb://localhost/test');
var Test = mongoose.model(
'Test',
new Schema({},{ strict: false }),
"testdata"
);
var testdata = [
{ "createDate": new Date("2015-07-20"), "value": 2 },
{ "createDate": new Date("2015-07-20"), "value": 4 },
{ "createDate": new Date("2015-07-22"), "value": 4 },
{ "createDate": new Date("2015-07-22"), "value": 6 },
{ "createDate": new Date("2015-07-24"), "value": 6 },
{ "createDate": new Date("2015-07-24"), "value": 8 }
];
var startDate = new Date("2015-07-20"),
endDate = new Date("2015-07-27"),
oneDay = 1000 * 60 * 60 * 24;
async.series(
[
function(callback) {
Test.remove({},callback);
},
function(callback) {
async.each(testdata,function(data,callback) {
Test.create(data,callback);
},callback);
},
function(callback) {
async.parallel(
[
function(callback) {
var tempDate = new Date( startDate.valueOf() );
async.whilst(
function() {
return tempDate.valueOf() <= endDate.valueOf();
},
function(callback) {
var day = tempDate.getUTCDate();
db.update(
{ "day": day },
{ "$inc": { "average": 0 } },
{ "upsert": true },
function(err) {
tempDate = new Date(
tempDate.valueOf() + oneDay
);
callback(err);
}
);
},
callback
);
},
function(callback) {
Test.aggregate(
[
{ "$match": {
"createDate": {
"$gte": startDate,
"$lt": new Date( endDate.valueOf() + oneDay )
}
}},
{ "$group": {
"_id": { "$dayOfMonth": "$createDate" },
"average": { "$avg": "$value" }
}}
],
function(err,results) {
if (err) callback(err);
async.each(results,function(result,callback) {
db.update(
{ "day": result._id },
{ "$inc": { "average": result.average } },
{ "upsert": true },
callback
)
},callback);
}
);
}
],
callback
);
}
],
function(err) {
if (err) throw err;
db.find({},{ "_id": 0 }).sort({ "day": 1 }).exec(function(err,result) {
console.log(result);
mongoose.disconnect();
});
}
);
这给出了这个输出:
[ { day: 20, average: 3 },
{ day: 21, average: 0 },
{ day: 22, average: 5 },
{ day: 23, average: 0 },
{ day: 24, average: 7 },
{ day: 25, average: 0 },
{ day: 26, average: 0 },
{ day: 27, average: 0 } ]
简而言之,"datastore" 是用 nedb
创建的,它的行为基本上与任何 MongoDB 集合相同(具有精简功能)。然后,您可以为任何结果插入 "keys" 范围内的预期值和默认值。
然后 运行 你的聚合语句,它只会 return 查询集合中存在的键,你只需 "update" 在与聚合值。
为了提高效率,我 运行 空结果 "creation" 和 parallel, utilizing "upsert" functionallity and the $inc
运算符中的 "aggregation" 操作值。这些不会冲突,这意味着创建可以在聚合的同时发生运行,所以没有延迟。
这很容易集成到您的 API 中,因此您可以拥有所有您想要的键,包括那些在输出集合中没有数据聚合的键。
同样的方法适用于在您的 MongoDB 服务器上使用另一个实际集合来处理非常大的结果集。但是如果它们非常大,那么无论如何您都应该预先聚合结果,并且只使用标准查询来抽样。
我正在尝试查询集合并检索过去 7 天(不包括当天)每一天的平均值。在某些或所有的日子里,可能没有平均值。
这是我目前的情况:
var dateTill = moment({hour:0,minute:0}).subtract(1, 'days')._d
var dateSevenDaysAgo = moment({hour:0,minute:0}).subtract(7, 'days')._d;
Rating.aggregate([
{
$match:{
userTo:facebookId,
timestamp:{$gt:dateSevenDaysAgo,$lt:dateTill}
}
},
{
$group:{
_id:{day:{'$dayOfMonth':'$timestamp'}},
average:{$avg:'$rating'}
}
},
{
$sort:{
'_id.day':1
}
}
]
这给了我
[ { _id: { day: 20 }, average: 1 },
{ _id: { day: 22 }, average: 3 },
{ _id: { day: 24 }, average: 5 } ]
我想要得到的是这样的:
[1,,3,,5,,]
表示最近 7 天的平均值,并且有一个空元素,表示当天没有平均值。
我可以尝试创建一个函数来检测差距在哪里,但是当平均值分布在两个不同的月份时,这将不起作用。例如 (July 28,29,30,31,Aug 1,2] - 八月的日子将被排序到我想要的数组的前面。
有更简单的方法吗?
谢谢!
人们经常询问 "empty results",他们的想法通常来自他们如何通过 SQL 查询来解决问题。
但是虽然 "possible" 为不包含分组键的项目抛出一组 "empty results",但这是一个困难的过程,很像 SQL 方法使用,它只是人为地将这些值放入语句中,它确实不是一个非常受性能驱动的替代方案。想想 "join" 使用一组制造的密钥。效率不高。
更聪明的方法是直接在客户端 API 准备好这些结果,而不发送到服务器。然后聚合输出可以 "merged" 与这些结果创建一个完整的集合。
无论您想要存储要合并的集合取决于您,它只需要基本的 "hash table" 和查找。但这里有一个使用 nedb 的示例,它允许您保持 MongoDB 套查询和更新的思维方式:
var async = require('async'),
mongoose = require('mongoose'),
DataStore = require('nedb'),
Schema = mongoose.Schema,
db = new DataStore();
mongoose.connect('mongodb://localhost/test');
var Test = mongoose.model(
'Test',
new Schema({},{ strict: false }),
"testdata"
);
var testdata = [
{ "createDate": new Date("2015-07-20"), "value": 2 },
{ "createDate": new Date("2015-07-20"), "value": 4 },
{ "createDate": new Date("2015-07-22"), "value": 4 },
{ "createDate": new Date("2015-07-22"), "value": 6 },
{ "createDate": new Date("2015-07-24"), "value": 6 },
{ "createDate": new Date("2015-07-24"), "value": 8 }
];
var startDate = new Date("2015-07-20"),
endDate = new Date("2015-07-27"),
oneDay = 1000 * 60 * 60 * 24;
async.series(
[
function(callback) {
Test.remove({},callback);
},
function(callback) {
async.each(testdata,function(data,callback) {
Test.create(data,callback);
},callback);
},
function(callback) {
async.parallel(
[
function(callback) {
var tempDate = new Date( startDate.valueOf() );
async.whilst(
function() {
return tempDate.valueOf() <= endDate.valueOf();
},
function(callback) {
var day = tempDate.getUTCDate();
db.update(
{ "day": day },
{ "$inc": { "average": 0 } },
{ "upsert": true },
function(err) {
tempDate = new Date(
tempDate.valueOf() + oneDay
);
callback(err);
}
);
},
callback
);
},
function(callback) {
Test.aggregate(
[
{ "$match": {
"createDate": {
"$gte": startDate,
"$lt": new Date( endDate.valueOf() + oneDay )
}
}},
{ "$group": {
"_id": { "$dayOfMonth": "$createDate" },
"average": { "$avg": "$value" }
}}
],
function(err,results) {
if (err) callback(err);
async.each(results,function(result,callback) {
db.update(
{ "day": result._id },
{ "$inc": { "average": result.average } },
{ "upsert": true },
callback
)
},callback);
}
);
}
],
callback
);
}
],
function(err) {
if (err) throw err;
db.find({},{ "_id": 0 }).sort({ "day": 1 }).exec(function(err,result) {
console.log(result);
mongoose.disconnect();
});
}
);
这给出了这个输出:
[ { day: 20, average: 3 },
{ day: 21, average: 0 },
{ day: 22, average: 5 },
{ day: 23, average: 0 },
{ day: 24, average: 7 },
{ day: 25, average: 0 },
{ day: 26, average: 0 },
{ day: 27, average: 0 } ]
简而言之,"datastore" 是用 nedb
创建的,它的行为基本上与任何 MongoDB 集合相同(具有精简功能)。然后,您可以为任何结果插入 "keys" 范围内的预期值和默认值。
然后 运行 你的聚合语句,它只会 return 查询集合中存在的键,你只需 "update" 在与聚合值。
为了提高效率,我 运行 空结果 "creation" 和 parallel, utilizing "upsert" functionallity and the $inc
运算符中的 "aggregation" 操作值。这些不会冲突,这意味着创建可以在聚合的同时发生运行,所以没有延迟。
这很容易集成到您的 API 中,因此您可以拥有所有您想要的键,包括那些在输出集合中没有数据聚合的键。
同样的方法适用于在您的 MongoDB 服务器上使用另一个实际集合来处理非常大的结果集。但是如果它们非常大,那么无论如何您都应该预先聚合结果,并且只使用标准查询来抽样。