我的文档结构类似于:
{
_id: ...,
key1: ....
key2: ....
....
min_value: //should be the minimum of all the values in options
options: [
{
source: 'a',
value: 12,
},
{
source: 'b',
value: 10,
},
...
]
},
{
_id: ...,
key1: ....
key2: ....
....
min_value: //should be the minimum of all the values in options
options: [
{
source: 'a',
value: 24,
},
{
source: 'b',
value: 36,
},
...
]
}
期权中各种来源的价值将不断更新(几分钟或几小时), 假设选项数组的大小没有改变,即没有额外的元素添加到列表
我的查询属于以下类型:
- 查找所有选项的min_value落在某个限制之间的所有文档。
我可以先对选项进行解除(然后取min),然后运行比较查询,但我是mongo的新手并且不确定性能如何 放松操作会影响。这类文件的数量约为几百万。
或者有没有人有任何改变文档结构的建议,这可以帮助我简化这个查询? (除了为每个源创建单独的文档 - 它将涉及大量数据重复) 谢谢!
答案 0 :(得分:2)
使用$unwind
确实相当昂贵,最值得注意的是使用更大的阵列,但在所有使用情况下都会有成本。在没有真正的结构变化的情况下,有两种方法可以在这里不需要$unwind
。
在基本情况下,从MongoDB 3.2.x版本系列开始,$min
运算符除了标准的分组累加器角色外,还可以在“投影”意义上直接处理值数组。这意味着在相关$map
运算符的帮助下处理数组的元素,然后您可以在不使用$unwind
的情况下获取最小值:
db.collection.aggregate([
// Still makes sense to use an index to select only possible documents
{ "$match": {
"options": {
"$elemMatch": {
"value": { "$gte": minValue, "$lt": maxValue }
}
}
}},
// Provides a logical filter to remove non-matching documents
{ "$redact": {
"$cond": {
"if": {
"$let": {
"vars": {
"min_value": {
"$min": {
"$map": {
"input": "$options",
"as": "option",
"in": "$$option.value"
}
}
}
},
"in": { "$and": [
{ "$gte": [ "$$min_value", minValue ] },
{ "$lt": [ "$$min_value", maxValue ] }
]}
}
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
// Optionally return the min_value as a field
{ "$project": {
"min_value": {
"$min": {
"$map": {
"input": "$options",
"as": "option",
"in": "$$option.value"
}
}
}
}}
])
基本情况是从数组中获取“最小”值(在$let
内完成,因为我们想在逻辑条件中使用“两次”结果。帮助我们不重复自己)是先提取来自"value"
数组的"options"
数据。这是使用$map
完成的。
$map
的输出是一个只包含这些值的数组,因此它作为$min
的参数提供,然后返回该数组的最小值。
使用$redact
有点像$match
管道阶段,不同之处在于,不是需要在正在检查的文档中“存在”字段,而是仅通过计算形成逻辑条件
在这种情况下,条件为$and
,$gte
和$lt
的逻辑形式的“两者”对计算值(从$let
返回{{1} }})。
"$$min_value"
阶段具有特殊参数,当条件为$redact
或$$KEEP
文件来自true
时,$$PRUNE
文件适用false
}}
这非常类似于在$project
然后$match
实际将值投影到文档中,然后在另一个阶段进行过滤,但所有这些都在一个阶段完成。当然,您可能希望在返回的内容中$project
得到结果字段,但如果您使用$redact
“首先”删除不匹配的文档,通常会减少工作量。
当然,我认为最佳选项是将"min_value"
字段实际保留在文档中,而不是在运行时进行处理。因此,在更新期间添加或更改数组项时,这是一件非常简单的事情。
为此,有$min
“更新”运算符。与$push
追加时使用它:
db.collection.update({
{ "_id": id },
{
"$push": { "options": { "source": "a", "value": 9 } },
"$min": { "min_value": 9 }
}
})
或更新元素的值时:
db.collection.update({
{ "_id": id, "options.source": "a" },
{
"$set": { "options.$.value": 9 },
"$min": { "min_value": 9 }
}
})
如果文档中的当前"min_value"
大于$min
中的参数,或者该密钥尚不存在,则将写入给定的值。如果它大于,则现有值保持不变,因为它已经是较小的值。
您甚至可以使用简单的“批量”操作更新来设置所有现有数据:
var ops = [];
db.collection.find({ "min_value": { "$exists": false } }).forEach(function(doc) {
// Queue operations
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$min": {
"min_value": Math.min.apply(
null,
doc.options.map(function(option) {
return option.value
})
)
}
}
}
});
// Write once in 1000 documents
if ( ops.length == 1000 ) {
db.collection.bulkWrite(ops);
ops = [];
}
});
// Clear any remaining operations
if ( ops.length > 0 )
db.collection.bulkWrite(ops);
然后使用一个字段,它只是一个简单的范围选择:
db.collection.find({
"min_value": {
"$gte": minValue, "$lt": maxValue
}
})
因此,在文档中保留一个字段(或者如果您经常需要不同条件的字段)确实应该符合您的最佳利益,因为这样可以提供最有效的查询。
当然,聚合$min
和$map
的新功能也可以在没有字段的情况下使用,如果您更喜欢更动态的条件。