如何通过调节嵌套对象数组来检索文档?
How to retrieve documents with conditioning an array of nested objects?
mongodb中存储的对象结构如下:
obj = {_id: "55c898787c2ab821e23e4661", ingredients: [{name: "ingredient1", value: "70.2"}, {name: "ingredient2", value: "34"}, {name: "ingredient3", value: "15.2"}, ...]}
我想做的是检索所有文档,其中特定成分的值大于任意数字。
更具体地说,假设我们要检索所有包含名称为"ingredient1"且其值大于50的成分的文档。
尝试以下方法我无法检索到所需的结果:
var collection = db.get('docs');
var queryTest = collection.find({$where: 'this.ingredients.name == "ingredient1" && parseFloat(this.ingredients.value) > 50'}, function(e, docs) {
console.log(docs);
});
有谁知道以特定数组元素名称和值为条件的正确查询是什么?
谢谢!
您真的不需要对数组的 $where
here, just use basic query operators with an $elemMatch
查询进行 JavaScript 求值。虽然这里的 "value" 元素实际上是字符串,但这并不是重点(正如我在本文末尾解释的那样)。重点是第一时间做对:
collection.find(
{
"ingredients": {
"$elemMatch": {
"name": "ingredient1",
"value": { "$gt": 50 }
}
}
},
{ "ingredients.$": 1 }
)
第二部分的$
即postional operator,只投影查询条件中匹配到的数组元素
这也比 JavaScript 评估快得多,因为评估代码不需要编译并使用本机编码运算符,而且可以使用 "index"在数组的 "name" 甚至 "value" 元素上帮助过滤匹配项。
如果您希望数组中有多个匹配项,那么 .aggregate()
命令是最佳选择。对于现代 MongoDB 版本,这非常简单:
collection.aggregate([
{ "$match": {
"ingredients": {
"$elemMatch": {
"name": "ingredient1",
"value": { "$gt": 50 }
}
}
}},
{ "$redact": {
"$cond": {
"if": {
"$and": [
{ "$eq": [ { "$ifNull": [ "$name", "ingredient1" ] }, "ingredient1" ] },
{ "$gt": [ { "$ifNull": [ "$value", 60 ] }, 50 ] }
]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
在引入 $filter
运算符的即将发布的版本中甚至更简单:
collection.aggregate([
{ "$match": {
"ingredients": {
"$elemMatch": {
"name": "ingredient1",
"value": { "$gt": 50 }
}
}
}},
{ "$project": {
"ingredients": {
"$filter": {
"input": "$ingredients",
"as": "ingredient",
"cond": {
"$and": [
{ "$eq": [ "$$ingredient.name", "ingredient1" ] },
{ "$gt": [ "$$ingredient.value", 50 ] }
]
}
}
}
}}
])
在这两种情况下,您实际上是 "filtering" 初始文档匹配后不匹配条件的数组元素。
此外,由于您的 "values" 现在实际上是 "strings",您真的应该将其更改为数字。这是一个基本过程:
var bulk = collection.initializeOrderedBulkOp(),
count = 0;
collection.find().forEach(function(doc) {
doc.ingredients.forEach(function(ingredient,idx) {
var update = { "$set": {} };
update["$set"]["ingredients." + idx + ".value"] = parseFloat(ingredients.value);
bulk.find({ "_id": doc._id }).updateOne(update);
count++;
if ( count % 1000 != 0 ) {
bulk.execute();
bulk = collection.initializeOrderedBulkOp();
}
})
]);
if ( count % 1000 != 0 )
bulk.execute();
这将修复数据,以便此处的查询表单正常工作。
这比使用 JavaScript $where
处理要好得多,后者需要评估集合中的每个文档,而没有索引进行过滤。正确的形式是:
collection.find(function() {
return this.ingredients.some(function(ingredient) {
return (
( ingredient.name === "ingredient1" ) &&
( parseFloat(ingredient.value) > 50 )
);
});
})
而且这也不能像其他形式那样"project"结果中的匹配值。
尝试使用 $elemMatch:
var queryTest = collection.find(
{ ingredients: { $elemMatch: { name: "ingredient1", value: { $gte: 50 } } } }
);
mongodb中存储的对象结构如下:
obj = {_id: "55c898787c2ab821e23e4661", ingredients: [{name: "ingredient1", value: "70.2"}, {name: "ingredient2", value: "34"}, {name: "ingredient3", value: "15.2"}, ...]}
我想做的是检索所有文档,其中特定成分的值大于任意数字。
更具体地说,假设我们要检索所有包含名称为"ingredient1"且其值大于50的成分的文档。
尝试以下方法我无法检索到所需的结果:
var collection = db.get('docs');
var queryTest = collection.find({$where: 'this.ingredients.name == "ingredient1" && parseFloat(this.ingredients.value) > 50'}, function(e, docs) {
console.log(docs);
});
有谁知道以特定数组元素名称和值为条件的正确查询是什么?
谢谢!
您真的不需要对数组的 $where
here, just use basic query operators with an $elemMatch
查询进行 JavaScript 求值。虽然这里的 "value" 元素实际上是字符串,但这并不是重点(正如我在本文末尾解释的那样)。重点是第一时间做对:
collection.find(
{
"ingredients": {
"$elemMatch": {
"name": "ingredient1",
"value": { "$gt": 50 }
}
}
},
{ "ingredients.$": 1 }
)
第二部分的$
即postional operator,只投影查询条件中匹配到的数组元素
这也比 JavaScript 评估快得多,因为评估代码不需要编译并使用本机编码运算符,而且可以使用 "index"在数组的 "name" 甚至 "value" 元素上帮助过滤匹配项。
如果您希望数组中有多个匹配项,那么 .aggregate()
命令是最佳选择。对于现代 MongoDB 版本,这非常简单:
collection.aggregate([
{ "$match": {
"ingredients": {
"$elemMatch": {
"name": "ingredient1",
"value": { "$gt": 50 }
}
}
}},
{ "$redact": {
"$cond": {
"if": {
"$and": [
{ "$eq": [ { "$ifNull": [ "$name", "ingredient1" ] }, "ingredient1" ] },
{ "$gt": [ { "$ifNull": [ "$value", 60 ] }, 50 ] }
]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
在引入 $filter
运算符的即将发布的版本中甚至更简单:
collection.aggregate([
{ "$match": {
"ingredients": {
"$elemMatch": {
"name": "ingredient1",
"value": { "$gt": 50 }
}
}
}},
{ "$project": {
"ingredients": {
"$filter": {
"input": "$ingredients",
"as": "ingredient",
"cond": {
"$and": [
{ "$eq": [ "$$ingredient.name", "ingredient1" ] },
{ "$gt": [ "$$ingredient.value", 50 ] }
]
}
}
}
}}
])
在这两种情况下,您实际上是 "filtering" 初始文档匹配后不匹配条件的数组元素。
此外,由于您的 "values" 现在实际上是 "strings",您真的应该将其更改为数字。这是一个基本过程:
var bulk = collection.initializeOrderedBulkOp(),
count = 0;
collection.find().forEach(function(doc) {
doc.ingredients.forEach(function(ingredient,idx) {
var update = { "$set": {} };
update["$set"]["ingredients." + idx + ".value"] = parseFloat(ingredients.value);
bulk.find({ "_id": doc._id }).updateOne(update);
count++;
if ( count % 1000 != 0 ) {
bulk.execute();
bulk = collection.initializeOrderedBulkOp();
}
})
]);
if ( count % 1000 != 0 )
bulk.execute();
这将修复数据,以便此处的查询表单正常工作。
这比使用 JavaScript $where
处理要好得多,后者需要评估集合中的每个文档,而没有索引进行过滤。正确的形式是:
collection.find(function() {
return this.ingredients.some(function(ingredient) {
return (
( ingredient.name === "ingredient1" ) &&
( parseFloat(ingredient.value) > 50 )
);
});
})
而且这也不能像其他形式那样"project"结果中的匹配值。
尝试使用 $elemMatch:
var queryTest = collection.find(
{ ingredients: { $elemMatch: { name: "ingredient1", value: { $gte: 50 } } } }
);