我有一个数组countries值的集合,如下所示。我想总结一下各国的价值观。
{
"_id": ObjectId("54cd5e7804f3b06c3c247428"),
"country_json": {
"AE": NumberLong("13"),
"RU": NumberLong("16"),
"BA": NumberLong("10"),
...
}
},
{
"_id": ObjectId("54cd5e7804f3b06c3c247429"),
"country_json": {
"RU": NumberLong("12"),
"ES": NumberLong("28"),
"DE": NumberLong("16"),
"AU": NumberLong("44"),
...
}
}
如何将各国的价值观相加,得出这样的结果?
{
"AE": 13,
"RU": 28,
..
}
这可以简单地使用aggregation
来完成
> db.test.aggregate([
{$project: {
RU: "$country_json.RU",
AE: "$country_json.AE",
BA: "$country_json.BA"
}},
{$group: {
_id: null,
RU: {$sum: "$RU"},
AE: {$sum: "$AE"},
BA: {$sum: "$BA"}
}
])
输出:
{
"_id" : null,
"RU" : NumberLong(28),
"AE" : NumberLong(13),
"BA" : NumberLong(10)
}
如果您打算像这样在"键"之间聚合统计信息,那么这不是一个很好的文档结构。无论如何,我并不喜欢"数据作为关键字名称",但主要的一点是,由于关键字名称在任何地方都不一样,它在许多MongoDB查询表单中都不"起作用"。
特别是对于聚合框架,存储数据的更好形式是在实际数组中,如下所示:
{
"_id": ObjectId("54cd5e7804f3b06c3c247428"),
"countries": [
{ "key": "AE", "value": NumberLong("13"),
{ "key": "RU", "value": NumberLong("16"),
{ "key": "BA", "value": NumberLong("10")
]
}
有了它,您可以简单地使用聚合操作:
db.collection.aggregate([
{ "$unwind": "$countries" },
{ "$group": {
"_id": "$countries.key",
"value": { "$sum": "$countries.value" }
}}
])
这会给你这样的结果:
{ "_id": "AE", "value": NumberLong(13) },
{ "_id": "RU", "value": NumberLong(28) }
这种结构确实与聚合框架和其他MongoDB查询模式"配合得很好",因为当你想以这种方式使用数据时,这确实是"期望"的方式。
在不改变文档结构的情况下,您必须使用JavaScript评估方法来遍历文档的密钥,因为这是使用MongoDB进行评估的唯一方法:
db.collection.mapReduce(
function() {
var country = this.country_json;
Object.keys(country).forEach(function(key) {
emit( key, country[key] );
});
},
function(key,values) {
return values.reduce(function(p,v) { return NumberLong(p+v) });
},
{ "out": { "inline": 1 } }
)
这将产生与聚合示例输出完全相同的结果,但使用当前文档结构。当然,JavaScript评估的使用不如聚合框架使用的本机方法有效,所以它的性能不会很好。
还要注意,在您的强制转换NumberLong
字段中"大值"可能存在的问题,因为它们以JavaScipt的方式表示的主要原因是JavaScipt本身对该值的大小有限制。很可能你的值只是微不足道的,但只是以这种方式"投射",但对于符合意图的足够大的数字,那么数学运算就会失败。
因此,考虑改变这些数据的结构以使事情变得更容易,通常是个好主意。最后要注意的是,对于单个文档中的所有键,您所期望的输出类型同样是反直觉的,因为它需要遍历"hash/map"的键,而不是使用数组或游标的自然迭代器。