我的Mongo数据库中有时间数据。每个文档等于一分钟并包含60秒作为具有每个值的对象。如何在一分钟内获得所有秒的平均值?
看起来像这样的文件:
{
"_id" : ObjectId("55575e4062771c26ec5f2287"),
"timestamp" : "2015-05-16T18:12:00.000Z",
"values" : {
"0" : "26.17",
"1" : "26.17",
"2" : "26.17",
...
"58" : "24.71",
"59" : "25.20"
}
}
答案 0 :(得分:2)
你可以采取两种方法:
$avg
运算符获得平均值让我们看看第一个选项。目前的情况是,由于values
子文档中的动态密钥,模式将无法使用聚合框架。有利于聚合框架的理想模式将使value字段成为包含嵌入式键/值文档的数组,如下所示:
/* 0 */
{
"_id" : ObjectId("5559d66c9bbec0dd0344e4b0"),
"timestamp" : "2015-05-16T18:12:00.000Z",
"values" : [
{
"k" : "0",
"v" : 26.17
},
{
"k" : "1",
"v" : 26.17
},
{
"k" : "2",
"v" : 26.17
},
...
{
"k" : "58",
"v" : 24.71
},
{
"k" : "59",
"v" : 25.20
}
]
}
使用MongoDB 3.6及更新版本,使用聚合框架通过 $objectToArray
运算符将哈希图转换为数组,然后使用 $avg
计算平均值。
考虑运行以下聚合管道:
db.test.aggregate([
{
"$addFields": {
"values": { "$objectToArray": "$values" }
}
}
])
使用这个新架构,您需要更新集合,通过迭代从aggregate方法返回的游标并使用 bulkWrite
将字符串值更改为int,如下所示:
var bulkUpdateOps = [],
cursor = db.test.aggregate([
{
"$addFields": {
"values": { "$objectToArray": "$values" }
}
}
]);
cursor.forEach(doc => {
const { _id, values } = doc;
let temp = values.map(item => {
item.key = item.k;
item.value = parseFloat(item.v) || 0;
delete item.k;
delete item.v;
return item;
});
bulkUpdateOps.push({
"updateOne": {
"filter": { _id },
"update": { "$set": { values: temp } },
"upsert": true
}
});
if (bulkUpdateOps.length === 1000) {
db.test.bulkWrite(bulkUpdateOps);
bulkUpdateOps = [];
}
});
if (bulkUpdateOps.length > 0) {
db.test.bulkWrite(bulkUpdateOps);
}
如果您的MongoDB版本不支持聚合框架中的 $objectToArray
运算符,那么要将当前架构转换为上面的架构,需要使用MongoDB的一些原生JavaScript函数{ {3}}光标的find()
函数如下(假设您有一个测试集合):
var bulkUpdateOps = [],
cursor = db.test.find();
cursor.forEach(doc => {
const { _id, values } = doc;
let temp = Object.keys(values).map(k => {
let obj = {};
obj.key = k;
obj.value = parseFloat(doc.values[k]) || 0;
return obj;
});
bulkUpdateOps.push({
"updateOne": {
"filter": { _id },
"update": { "$set": { values: temp } },
"upsert": true
}
});
if (bulkUpdateOps.length === 1000) {
db.test.bulkWrite(bulkUpdateOps);
bulkUpdateOps = [];
}
});
if (bulkUpdateOps.length > 0) {
db.test.bulkWrite(bulkUpdateOps);
}
或
db.test.find().forEach(function (doc){
var keys = Object.keys(doc.values),
values = keys.map(function(k){
var obj = {};
obj.key = k;
obj.value = parseFloat(doc.values[k]) || 0;
return obj;
});
doc.values = values;
db.test.save(doc);
});
该集合现在将具有上述模式,因此遵循聚合管道,该管道将在一分钟内为您提供平均时间:
db.test.aggregate([
{
"$fields": {
"average": { "$avg": "$values.value" }
}
}
])
或者对于MongoDB 3.0及更低版本
db.test.aggregate([
{ "$unwind": "$values" },
{
"$group": {
"_id": "$timestamp",
"average": {
"$avg": "$values.value"
}
}
}
])
对于上述文件,输出将为:
/* 0 */
{
"result" : [
{
"_id" : "2015-05-16T18:12:00.000Z",
"average" : 25.684
}
],
"ok" : 1
}
对于其他 forEach()
选项,操作背后的直觉是您将使用JavaScript进行必要的转换并计算最终平均值。您需要定义三个函数:
<强>地图
当你告诉Mongo MapReduce时,你提供的作为map函数的函数将接收每个文档作为this参数。地图的目的是在JavaScript中运用您需要的任何逻辑,然后调用emit 0次或更多次以产生可简化的值。
var map = function(){
var obj = this.values;
var keys = Object.keys(obj);
var values = [];
keys.forEach(function(key){
var val = parseFloat(obj[key]);
var value = { count: 1, qty: val };
emit(this.timestamp, value);
});
};
对于每个文档,您需要发出一个键和一个值。键是emit函数的第一个参数,表示您希望如何对值进行分组(在这种情况下,您将按时间戳进行分组)。要发出的第二个参数是值,在这种情况下,它是一个小对象,包含文档计数(始终为1)和每个单值对象键的总值,即每分钟内的每秒。
<强>减少强>
接下来,您需要定义reduce函数,其中Mongo将您发出的项目分组并将它们作为数组传递给此reduce函数它在reduce函数内部,您要进行聚合计算并减少所有对象到单个对象。
var reduce = function(key, values) {
var result = {count: 0, total: 0 };
values.forEach(function(value){
result.count += value.count;
result.total += value.qty;
});
return result;
};
此reduce函数返回单个结果。返回值与发射值具有相同的形状非常重要。对于给定的键,MongoDB也可以多次调用reduce函数并要求您处理一组部分值,因此如果需要执行一些最终计算,还可以为MapReduce提供一个finalize函数。
<强>的Finalize 强>
finalize函数是可选的,但是如果你需要根据完全缩减的数据集计算某些东西,你将要使用finalize函数。在完成对集合的所有reduce调用之后,Mongo将调用finalize函数。这将是计算文档/时间戳中所有第二个值的平均值的地方:
var finalize = function (key, value) {
value.average = value.total / value.count;
return value;
};
将它放在一起
有了JavaScript,剩下的就是告诉MongoDB执行MapReduce:
var map = function(){
var obj = this.values;
var keys = Object.keys(obj);
var values = [];
keys.forEach(function(key){
var val = parseFloat(obj[key]);
var value = { count: 1, qty: val };
emit(this.timestamp, value);
});
};
var reduce = function(key, values) {
var result = {count: 0, total: 0 };
values.forEach(function(value){
result.count += value.count;
result.total += value.qty;
});
return result;
};
var finalize = function (key, value) {
value.average = value.total / value.count;
return value;
};
db.collection.mapReduce(
map,
reduce,
{
out: { merge: "map_reduce_example" },
finalize: finalize
}
)
当您查询输出集合map_reduce_example,db.map_reduce_example.find()
时,您会得到结果:
/* 0 */
{
"_id" : null,
"value" : {
"count" : 5,
"total" : 128.42,
"average" : 25.684
}
}
<强>参考强>:
答案 1 :(得分:-1)
这种数据结构会产生大量冲突并且难以处理mongo操作。在这种情况下,您要么更改了架构设计。但是,如果您无法更改此架构,请按照以下步骤操作:
在您的架构中有两个主要问题1> keys dynamic and 2> values of given keys in string
,因此您应该使用一些编程代码来计算avg
检查以下脚本
从ref this首次计算的values
Object.size = function(obj) {
var size = 0,
key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
db.collectionName.find().forEach(function(myDoc) {
var objects = myDoc.values;
var value = 0;
// Get the size of an object
var size = Object.size(objects);
for (var key in objects) {
value = value + parseFloat(objects[key]); // parse string values to float
}
var avg = value / size
print(value);
print(size);
print(avg);
});