我想找到Mongo Document中嵌套字段的所有唯一值的频率计数器。
更具体地说,如果我的收藏集说db ['sample'],则包含以下文档-
{'a' : 1, 'b' : {'c' : 25, 'd' : "x", 'e' : 36}},
{'a' : 2, 'b' : {'c' : 5, 'd' : "xx", 'e' : 36}},
{'a' : 33, 'b' : {'c' : 25, 'd' : "xx", 'e' : 36}},
{'a' : 17, 'b' : {'c' : 25, 'd' : "xxx", 'e' : 36}},
如何获取“ d”字段所有唯一值的频率计数器?即我的输出应为{'d':{“ xx”:2,“ x”:1,“ xxx”:1}}
这甚至可能吗?感谢对此的任何帮助。谢谢。
我查看了有关聚合和objectToArray转换的文档,以将地图转换为数组,并在PyMongo中尝试了以下操作
1)
db['sample'].aggregate([ { "$addFields" : { "b" : {"$objectToArray" : "$b"}}},\
{"$unwind" : "$b"},\
{"$group" : { "_id" : "$b.k",\
"count" : {"$sum" : "$b.v"}}} ])
这将在可能的情况下给出每个字段的累计计数-例如'c':25 + 5 + 25 + 25。
2)
db['sample'].aggregate([ { "$addFields" : { "b" : {"$objectToArray" : "$b"}}},\
{"$unwind" : "$b"}, \
{"$group" : { "_id" : "$b.k", \
"count" : {"$sum" : 1 }}} ])
这给出了字段在文档中出现的总次数-'c':4,'d':4等。
答案 0 :(得分:0)
您基本上是在以错误的方式进行处理。您可以清楚地找到"b.d"
作为要聚合的键,因此无需将其转换为数组:
cursor = db.sample.aggregate([
{ "$group": {
"_id": "$b.d",
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": None,
"data": { "$push": { "k": "$_id", "v": "$count" } }
}},
{ "$replaceRoot": {
"newRoot": { "$arrayToObject": "$data" }
}}
])
for doc in cursor:
print(doc)
返回
{ 'x': 1, 'xx': 2, 'xxx': 1 }
但这实际上是过大的,因为实际上所有工作都是在最初的$group
语句中完成的。您真正需要做的就是运行该程序并获取结果,并将其组合为单个字典作为所需的输出:
cursor = db.sample.aggregate([
{ "$group": {
"_id": "$b.d",
"count": { "$sum": 1 }
}}
])
data = list(cursor)
result = reduce(
lambda x,y:
dict(x.items() + { y['_id']: y['count'] }.items()), data,{})
print(result)
返回完全相同的东西:
{ 'x': 1, 'xx': 2, 'xxx': 1 }
此外,它不需要添加其他聚合阶段和运算符就不需要$group
响应基本上是从服务器返回的,因此您没有更改服务器真正返回的内容:
{ "_id" : "xxx", "count" : 1 }
{ "_id" : "xx", "count" : 2 }
{ "_id" : "x", "count" : 1 }
因此,真正的教训是,当您可以在聚合管道中执行花哨的操作时,您真正应该考虑的是,当替代方法更清洁时,您可能不应该以及更具可读性的代码。
作为参考,尽管发生的只是额外的$group
使用$push
来创建带有k
和v
键的数组,这在下一个管道阶段中是可以预期的。下一阶段使用$replaceRoot
从上一阶段创建的数组中获取$arrayToObject
的输出,并将其基本上转换为对象/字典。
相比之下,reduce
做的事情完全相同。我们基本上将游标结果放入list
中,以便python函数可以对该列表进行操作。然后,只需遍历该列表中始终以_id
作为键和“已计数”输出的另一个命名属性(此处使用count
)的文件,然后将其转换为<最终字典输出的strong>键和值对。
只是出于娱乐目的,基于您最初的尝试可能是:
db.sample.aggregate([
{ "$addFields": { "b": { "$objectToArray": "$b" } } },
{ "$unwind": "$b" },
{ "$group": {
"_id": {
"_id": "$b.k",
"k": "$b.v"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id._id",
"data": { "$push": { "k": { "$toString": "$_id.k" }, "v": "$count" } }
}},
{ "$addFields": {
"data": { "$arrayToObject": "$data" }
}}
])
哪个会返回:
{ "_id" : "c", "data" : { "25" : 3, "5" : 1 } }
{ "_id" : "e", "data" : { "36" : 4 } }
{ "_id" : "d", "data" : { "xxx" : 1, "xx" : 2, "x" : 1 } }
同样,不使用其他流水线阶段进行转换的相同结果来自将map
和reduce
与python一起使用:
cursor = db.sample.aggregate([
{ "$addFields": { "b": { "$objectToArray": "$b" } } },
{ "$unwind": "$b" },
{ "$group": {
"_id": {
"_id": "$b.k",
"k": "$b.v"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id._id",
"data": { "$push": { "k": "$_id.k", "v": "$count" } }
}}
])
data = list(cursor)
result = map(lambda d: {
'_id': d['_id'],
'data': reduce(lambda x,y:
dict(x.items() + { y['k']: y['v'] }.items()), d['data'],
{})
},data)