python - 如何使用计数所有元素并按一个请求分组来进行 pymongo 聚合
问题描述
我有一个这样的字段集合:
{
"_id":"5cf54857bbc85fd0ff5640ba",
"book_id":"5cf172220fb516f706d00591",
"tags":{
"person":[
{"start_match":209, "length_match":6, "word":"kimmel"}
],
"organization":[
{"start_match":107, "length_match":12, "word":"philadelphia"},
{"start_match":209, "length_match":13, "word":"kimmel center"}
],
"location":[
{"start_match":107, "length_match":12, "word":"philadelphia"}
]
},
"deleted":false
}
我想收集类别中的不同单词并计算它。所以,输出应该是这样的:
{
"response": [
{
"tag": "location",
"tag_list": [
{
"count": 31,
"phrase": "philadelphia"
},
{
"count": 15,
"phrase": "usa"
}
]
},
{
"tag": "organization",
"tag_list": [ ... ]
},
{
"tag": "person",
"tag_list": [ ... ]
},
]
}
像这样的管道有效:
def pipeline_func(tag):
return [
{'$replaceRoot': {'newRoot': '$tags'}},
{'$unwind': '${}'.format(tag)},
{'$group': {'_id': '${}.word'.format(tag), 'count': {'$sum': 1}}},
{'$project': {'phrase': '$_id', 'count': 1, '_id': 0}},
{'$sort': {'count': -1}}
]
但它会为每个标签发出请求。我想知道如何在一个请求中完成它。感谢您的关注。
解决方案
如前所述,问题数据与当前声明的管道过程略有$unwind
不匹配,因为只能在数组上使用,并且tags
问题中呈现的不是数组。
对于问题中提供的数据,您基本上需要这样的管道:
db.collection.aggregate([
{ "$addFields": {
"tags": { "$objectToArray": "$tags" }
}},
{ "$unwind": "$tags" },
{ "$unwind": "$tags.v" },
{ "$group": {
"_id": {
"tag": "$tags.k",
"phrase": "$tags.v.word"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.tag",
"tag_list": {
"$push": {
"count": "$count",
"phrase": "$_id.phrase"
}
}
}}
])
再次根据注释,由于tags
实际上是一个对象,那么您实际上需要什么才能根据问题所要求的子键收集数据,就是将其本质上变成一个项目数组。
在您当前的管道中使用$replaceRoot
似乎表明它在这里$objectToArray
是合理使用的,因为它可以从 MongoDB 3.4 的后续补丁版本中获得,这是您现在应该在生产中使用的最小版本。
这$objectToArray
实际上与名称所说的差不多,并产生了一个数组(或“列表”,更Pythonic)分成键和值对的条目。这些本质上是对象(或“dict”条目)的“列表”,它们分别具有键k
和v
。在提供的文档中,第一个管道阶段的输出如下所示:
{
"book_id": "5cf172220fb516f706d00591",
"tags": [
{
"k": "person",
"v": [
{
"start_match": 209,
"length_match": 6,
"word": "kimmel"
}
]
}, {
"k": "organization",
"v": [
{
"start_match": 107,
"length_match": 12,
"word": "philadelphia"
}, {
"start_match": 209,
"length_match": 13,
"word": "kimmel center"
}
]
}, {
"k": "location",
"v": [
{
"start_match": 107,
"length_match": 12,
"word": "philadelphia"
}
]
}
],
"deleted" : false
}
因此,您应该能够看到现在如何轻松访问这些k
值并在grouping中使用它们,当然这v
也是标准数组。所以它只是如图所示的两个 $unwind
阶段,然后是两个 $group
阶段。作为第一个$group
收集键组合,第二个根据主分组键收集,同时将其他累积添加到该条目中的“列表” 。
当然,上面列表的输出并不完全是你在问题中所要求的,但数据基本上在那里。您可以选择添加一个$addFields
or$project
阶段以实质上将_id
键重命名为最终聚合阶段:
{ "$addFields": {
"_id": "$$REMOVE",
"tag": "$_id"
}}
或者简单地在光标输出上做一些pythonic的事情:
cursor = db.collection.aggregate([
{ "$addFields": {
"tags": { "$objectToArray": "$tags" }
}},
{ "$unwind": "$tags" },
{ "$unwind": "$tags.v" },
{ "$group": {
"_id": {
"tag": "$tags.k",
"phrase": "$tags.v.word"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.tag",
"tag_list": {
"$push": {
"count": "$count",
"phrase": "$_id.phrase"
}
}
}}
])
output = [{ 'tag': doc['_id'], 'tag_list': doc['tag_list'] } for doc in cursor]
print({ 'response': output });
最终输出为“列表”,您可以将其用于response
:
{
"tag_list": [
{
"count": 1,
"phrase": "philadelphia"
}
],
"tag": "location"
},
{
"tag_list": [
{
"count": 1,
"phrase": "kimmel"
}
],
"tag": "person"
},
{
"tag_list": [
{
"count": 1,
"phrase": "kimmel center"
}, {
"count": 1,
"phrase": "philadelphia"
}
],
"tag": "organization"
}
请注意,使用列表理解方法,您可以更好地控制“键”作为输出的顺序,因为 MongoDB 本身只会在投影中附加新键名,从而使现有键首先排序。如果这种事情对你很重要,那就是。虽然它真的不应该是因为所有类似 Object/Dict 的结构都不应该被认为具有任何键的设置顺序。这就是数组(或列表)的用途。
推荐阅读
- javascript - 如何在 Typescript 上设置 location.href 而不会出错?
- javascript - 如何在不刷新页面的情况下使用 JavaScript 显示当前时间
- python - 正则表达式定位编号列表不起作用
- node.js - 当只有 1 个 google 帐户登录时,Passport Google Oauth2 不提示选择帐户
- elasticsearch - 在logstash或弹性搜索中加入两个单独的日志文件以在kibana中的一行中查看?
- kotlin - 插入以使用 Kotlin 加入表 Spring JPA
- jenkins - 如何将 perScenarioTimeout 设置为 uftScenarioLoad?
- python - 有没有办法使用 Python 创建一个简单的基于命令行的文本编辑器,例如 Nano?
- python - 如何使用日期时间索引删除 DataFrame 中的多个时间段?
- javascript - 设置范围保护每周基于时间的触发