我正在使用MongoDB 3.4和PyMongo。我有一组关键字:
keywords = [ 'bar', 'foo', ..., 'zoo' ]
我也有一个系列:
docs = { 'data' : ' ... bar foo ... ',
'data' : ' ... foo ... ',
'data' : ' ... zoo ... ' }
我正在寻找一个PyMongo聚合查询,它会给我一个字典:
{ 'bar' : 0, 'foo' : 2, ..., 'zoo' : 0 }
答案 0 :(得分:1)
没有任何特定于语言的语言,因为唯一的解决方案是全部聚合或使用mapReduce,后者在JavaScript函数中定义
只需设置一些示例数据:
db.wordstuff.insertMany([
{ 'data': "foo brick bar" },
{ 'data': "brick foo" },
{ 'data': "bar brick baz" },
{ 'data': "bax" },
{ 'data': "brin brok fu foo" }
])
然后你可以运行聚合语句:
db.wordstuff.aggregate([
{ "$project": {
"_id": 0,
"split": {
"$filter": {
"input": { "$split": [ "$data", " " ] },
"cond": { "$in": [ "$$this", ["bar","foo","baz","blat"] ] }
}
}
}},
{ "$unwind": "$split" },
{ "$group": { "_id": "$split", "count": { "$sum": 1 } }},
{ "$group": {
"_id": null,
"data": { "$push": { "k": "$_id", "v": "$count" } }
}},
{ "$replaceRoot": {
"newRoot": {
"$arrayToObject": {
"$map": {
"input": ["bar","foo","baz","blat"],
"as": "d",
"in": {
"$cond": {
"if": { "$ne": [{ "$indexOfArray": ["$data.k","$$d"] },-1] },
"then": {
"$arrayElemAt": [
"$data",
{ "$indexOfArray": ["$data.k","$$d"] }
]
},
"else": { "k": "$$d", "v": 0 }
}
}
}
}
}
}}
])
实际上,所有的实际工作都是在这一点上完成的:
db.wordstuff.aggregate([
{ "$project": {
"_id": 0,
"split": {
"$filter": {
"input": { "$split": [ "$data", " " ] },
"cond": { "$in": [ "$$this", ["bar","foo","baz","blat"] ] }
}
}
}},
{ "$unwind": "$split" },
{ "$group": { "_id": "$split", "count": { "$sum": 1 } }},
])
这给你的输出如下:
{ "_id" : "baz", "count" : 1.0 }
{ "_id" : "bar", "count" : 2.0 }
{ "_id" : "foo", "count" : 3.0 }
所以这里真正的工作是由$split
完成的,这是使用聚合框架的主要依赖,所以至少为了做到这一点你需要MongoDB 3.4。非常简单的前提是$split
将单词作为数组成员单独输出,然后$filter
将内容与要匹配的输入数组匹配。
$filter
使用$in
,这是MongoDB 3.4的另一个补充,以匹配每个列出的单词。还有其他运算符可以使用更长的语法来执行此操作,但我们知道我们已经需要MongoDB 3.4,因此这是最短的语法。
之后真正完成的是$unwind
来自每个文档的匹配数组,然后$group
将这些匹配的单词作为不同的列表以及出现次数获得。
从数据库的主要角度来看,这就是它的全部内容。
以下部分实际上是"可选"因为它们很容易在代码中重现,并且通过这样做可能看起来更清晰,更清晰。但只是为了演示需要MongoDB 3.4.4的新运算符,至少是为了引入$arrayToObject
。
基本原则是下一个$group
"汇总"将光标中匹配的单词转换为单个文档中的数组。由于以后的原因,"k"
和"v"
也应用了非常具体的关键命名。
然后使用$replaceRoot
阶段,因为返回的文档内容是从表达式计算的。该表达式使用$map
迭代"输入数组"单词和匹配从聚合创建的条目。使用$indexOfArray
完成此匹配。返回匹配值的匹配索引。
您在$cond
中使用此功能,因为您要使用$arrayElemAt
将该值转换为匹配的元素,或者可选择识别索引不匹配。这将返回聚合条目(从早期匹配中获得)或"默认"给定单词的0
值。
最后一部分使用$arrayToObject
将具有属性"k"
和"v"
的对象数组转换为"键/值"成对作为对象。
因此您可以要求MongoDB执行此操作,但实际上数据实际上是通过最小管道减少的,因此您也可以在客户端代码中执行此操作。它非常简单,对于JavaScript,您只需:
var words = db.wordstuff.aggregate([
{ "$project": {
"_id": 0,
"split": {
"$filter": {
"input": { "$split": [ "$data", " " ] },
"cond": { "$in": [ "$$this", ["bar","foo","baz","blat"] ] }
}
}
}},
{ "$unwind": "$split" },
{ "$group": { "_id": "$split", "count": { "$sum": 1 } }},
]).toArray();
var result = ["bar","foo","baz","blat"].map(
w => ( words.map(wd => wd._id).indexOf(w) !== -1)
? words[words.map(wd => wd._id).indexOf(w)]
: { _id: w, count: 0 }
).reduce((acc,curr) => Object.assign(acc,{ [curr._id]: curr.count }),{})
因此,如果有任何语言特定的话,那就是其中的一部分。因此,如果您选择在其基础上运行聚合并处理生成的游标,那么python代码将是:
input = ["bar","foo","baz","blat"]
words = list(db.wordstuff.aggregate([
{ "$project": {
"_id": 0,
"split": {
"$filter": {
"input": { "$split": [ "$data", " " ] },
"cond": { "$in": [ "$$this", input ] }
}
}
}},
{ "$unwind": "$split" },
{ "$group": { "_id": "$split", "count": { "$sum": 1 } }},
]))
result = reduce(
lambda x,y:
dict(x.items() + { y['_id']: y['count'] }.items()),
map(lambda w: words[map(lambda wd: wd['_id'],words).index(w)]
if w in map(lambda wd: wd['_id'],words)
else { '_id': w, 'count': 0 },
input
),
{}
)
任何一种方法都会得出相同的结果:
{
"bar" : 2.0,
"foo" : 3.0,
"baz" : 1.0,
"blat" : 0.0
}
您甚至没有可用的最小MongoDB 3.4.0的替代情况是使用mapReduce代替该过程。同样,这需要作为JavaScript发送到服务器,这通常表示在"字符串"在大多数语言实现中(除了JavaScript本身):
db.wordstuff.mapReduce(
function() {
this.data.split(' ')
.filter( w => words.indexOf(w) !== -1 )
.forEach( w => emit(null,{ [w]: 1 }) );
},
function(key,values) {
return [].concat.apply([],
values.map(v => Object.keys(v).map(k => ({ k: k, v: v[k] })))
).reduce((acc,curr) => Object.assign(acc,{
[curr.k]: (acc.hasOwnProperty(curr.k))
? acc[curr.k] + curr.v : curr.v
}),{});
},
{
"out": { "inline": 1 },
"scope": { "words": ["bar","foo","baz","blat"] },
"finalize": function(key,value) {
return words.map( w => (value.hasOwnProperty(w))
? { [w]: value[w] } : { [w]: 0 }
).reduce((acc,curr) => Object.assign(acc,curr),{})
}
}
)
这会给你相同的结果,并且确实做同样的事情。稍微慢一些,因为MongoDB需要评估和处理JavaScript,而不是使用它自己的本地编码方法和聚合框架。