Javascript 计算键的值的出现次数
我有许多具有许多属性的文档。经过一个特定的Javascript 计算键的值的出现次数,javascript,mongodb,mapreduce,mongodb-query,aggregation-framework,Javascript,Mongodb,Mapreduce,Mongodb Query,Aggregation Framework,我有许多具有许多属性的文档。经过一个特定的$match过程后,我得到了一个小节。这里是简化的: [ {"name": "foo", "code": "bbb"}, {"name": "foo", "code": "aaa"}, {"name": "foo", "code": "aaa"}, {"name": "foo", "code": "aaa"}, {"name": "bar", "code": "aaa"}, {"name": "bar",
$match
过程后,我得到了一个小节。这里是简化的:
[
{"name": "foo", "code": "bbb"},
{"name": "foo", "code": "aaa"},
{"name": "foo", "code": "aaa"},
{"name": "foo", "code": "aaa"},
{"name": "bar", "code": "aaa"},
{"name": "bar", "code": "aaa"},
{"name": "bar", "code": "aaa"},
{"name": "baz", "code": "aaa"},
{"name": "baz", "code": "aaa"}
]
{
"name": {
"foo": 4,
"bar": 3,
"baz": 2
},
"code": {
"bbb": 1,
"aaa": 8
}
}
{
"name": ["foo", "foo", "foo", "foo", "bar", "bar", "bar", "baz", "baz"],
"code": ["bbb", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", "aaa", ]
}
我想计算某些属性的发生率,因此我最后得出以下结论(简化):
(或者类似的东西,我可以在之后用Node.js“翻译”)
我已经做了一个$group
阶段来计算其他属性(不同)。理想情况下,我将$addToSet
,并计算类似值添加到集合中的次数。但我不知道怎么做
或者,我想用$push
来结束这个(简化的):
但我也不知道如何将其转化为(接近)上述假设结果
仅对于单个字段,我可以使用上面的$push
,然后使用$group
:
"$group": {
"_id": {"_id": "$_id", "name": "$name"},
"nameCount": {"$sum": 1}
}
现在我有了\u id.name
和namecont
。但我已经失去了所有之前统计的属性,大约20个
有没有办法做我想做的事
注意:使用MongoDB 3.2
对于MongoDB 3.2,如果您想在返回的文档中以“键”的形式返回“数据”值,那么您几乎只能使用mapReduce。然而,需要考虑的是,你实际上不需要MangGDB来为你做这一部分。但要考虑的方法: 地图缩小 返回:
{
"_id" : null,
"value" : {
"name" : {
"foo" : 4.0,
"bar" : 3.0,
"baz" : 2.0
},
"code" : {
"bbb" : 1.0,
"aaa" : 8.0
}
}
}
总数的 对于MongoDB 3.4及以上版本,您可以使用
$arrayToObject
将其重塑为“键/值”对象。而且比简单地使用$push
制作两个大型阵列要高效一些,在现实世界中,这几乎肯定会突破BSON限制
此“或多或少”反映了mapReduce()
操作:
db.stuff.aggregate([
{ "$project": {
"_id": 0,
"data": [
{ "k": "name", "v": { "k": "$name", "count": 1 } },
{ "k": "code", "v": { "k": "$code", "count": 1 } }
]
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": { "k": "$data.k", "v": "$data.v.k" },
"count": { "$sum": "$data.v.count" }
}},
{ "$group": {
"_id": "$_id.k",
"v": { "$push": { "k": "$_id.v", "v": "$count" } }
}},
{ "$group": {
"_id": null,
"data": { "$push": { "k": "$_id", "v": "$v" } }
}},
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$map": {
"input": "$data",
"in": {
"k": "$$this.k",
"v": { "$arrayToObject": "$$this.v" }
}
}
}
}
}}
])
具有类似输出(无需通过应用$sort
强制对键进行排序):
因此,只有在我们实际使用新功能的最后阶段,输出才非常相似,并且很容易在代码中进行重塑:
{
"_id" : null,
"data" : [
{
"k" : "code",
"v" : [
{
"k" : "bbb",
"v" : 1.0
},
{
"k" : "aaa",
"v" : 8.0
}
]
},
{
"k" : "name",
"v" : [
{
"k" : "baz",
"v" : 2.0
},
{
"k" : "foo",
"v" : 4.0
},
{
"k" : "bar",
"v" : 3.0
}
]
}
]
}
所以事实上我们可以做到这一点:
db.stuff.aggregate([
{ "$project": {
"_id": 0,
"data": [
{ "k": "name", "v": { "k": "$name", "count": 1 } },
{ "k": "code", "v": { "k": "$code", "count": 1 } }
]
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": { "k": "$data.k", "v": "$data.v.k" },
"count": { "$sum": "$data.v.count" }
}},
{ "$group": {
"_id": "$_id.k",
"v": { "$push": { "k": "$_id.v", "v": "$count" } }
}},
{ "$group": {
"_id": null,
"data": { "$push": { "k": "$_id", "v": "$v" } }
}},
/*
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$map": {
"input": "$data",
"in": {
"k": "$$this.k",
"v": { "$arrayToObject": "$$this.v" }
}
}
}
}
}}
*/
]).map( doc =>
doc.data.map( d => ({
k: d.k,
v: d.v.reduce((acc,curr) =>
Object.assign(acc,{ [curr.k]: curr.v })
,{}
)
})).reduce((acc,curr) =>
Object.assign(acc,{ [curr.k]: curr.v })
,{}
)
)
这表明,仅仅因为聚合框架没有在早期版本的输出中使用“命名键”的特性,所以您通常不需要它们。因为我们实际使用新特性的唯一地方是在“最终”阶段,但是我们可以通过简单地在客户机代码中重塑最终输出来轻松地做到这一点
当然,结果也是一样的:
[
{
"code" : {
"bbb" : 1.0,
"aaa" : 8.0
},
"name" : {
"baz" : 2.0,
"foo" : 4.0,
"bar" : 3.0
}
}
]
{
"name" : {
"foo" : 4.0,
"bar" : 3.0,
"baz" : 2.0
},
"code" : {
"bbb" : 1.0,
"aaa" : 8.0
}
}
因此,它有助于了解应用此类转换时实际需要的确切“位置”。这里是“结束”,因为我们在任何“聚合”阶段都不需要它,因此您只需重塑聚合框架本身可以提供的最佳结果
坏方法 如前所述,到目前为止,您的尝试对于小数据来说可能还不错,但在大多数实际情况下,将集合中的所有项“推”到单个文档中而不进行缩减将打破16MB BSON限制 如果它真的在下面,那么你可以使用类似于这个怪物的东西: 产生:
{
"name" : {
"foo" : 4.0,
"bar" : 3.0,
"baz" : 2.0
},
"code" : {
"bbb" : 1.0,
"aaa" : 8.0
}
}
或者客户机代码中的缩减过程:
db.stuff.aggregate([
{ "$group": {
"_id": null,
"name": { "$push": "$name" },
"code": { "$push": "$code" }
}},
]).map( doc =>
["name","code"].reduce((acc,curr) =>
Object.assign(
acc,
{ [curr]: doc[curr].reduce((acc,curr) =>
Object.assign(acc,
(acc.hasOwnProperty(curr))
? { [curr]: acc[curr] += 1 }
: { [curr]: 1 }
),{}
)
}
),
{}
)
)
这也有同样的结果:
[
{
"code" : {
"bbb" : 1.0,
"aaa" : 8.0
},
"name" : {
"baz" : 2.0,
"foo" : 4.0,
"bar" : 3.0
}
}
]
{
"name" : {
"foo" : 4.0,
"bar" : 3.0,
"baz" : 2.0
},
"code" : {
"bbb" : 1.0,
"aaa" : 8.0
}
}
你的回答是无价的,我会仔细研究。请不要移除它的任何部分。但是,由于我的人为限制,我忘了提到最初的文档集是通过
$match
获得的,因此需要计算最终的集合。我猜Map Reduce是这样的。我还受到使用MongoDB 3.2的SLA限制的限制,否则我会对$ArrayTobObject
和$replaceRoot
大发雷霆。第一个$match
阶段根据用户输入动态进行,并返回1到50000个文档之间的任何集合。我在做一些类似于你最糟糕的例子。它适用于测试数据,但我知道这对于最终产品来说是不可持续的。我喜欢你为了说明的目的注释掉$replaceRoot
。我将进一步研究你答案的这一部分,看看它是否有效。因为初始的$group
已经包含了一些$sum
计数,但是如果我需要从$project
开始,我就不能这样做。我必须想办法在一次传球中做到这两个。最后,使用$first
@Redsandro重新设置$group
中的所有属性。更大的主体实际上表示,您根本不需要新功能,因为结果的“转换”部分始终可以在“实际聚合部分完成”之后完成。另外,它本质上与$match
相同。但更广泛地说,这个答案的收件人是“你问的问题”,因此,对“问题范围之外”的其他数据的任何考虑都不是这个答案的问题。@Redsandro当然,当你发现答案有用时,正确的做法是
{
"name" : {
"foo" : 4.0,
"bar" : 3.0,
"baz" : 2.0
},
"code" : {
"bbb" : 1.0,
"aaa" : 8.0
}
}