【问题标题】:How to retrieve documents with conditioning an array of nested objects?如何通过调节嵌套对象数组来检索文档?
【发布时间】:2015-08-22 11:07:27
【问题描述】:

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);
                                });

有谁知道以特定数组元素名称和值为条件的正确查询是什么?

谢谢!

【问题讨论】:

  • 我在这里注意到您的“值”条目实际上是字符串。因此,“500”下的基本词法比较将起作用(字符串方式),但最好将它们转换为存储中的数字。

标签: javascript mongodb mongodb-query aggregation-framework


【解决方案1】:

您在这里真的不需要$where 的JavaScript 评估,只需使用基本的查询运算符和$elemMatch 查询数组即可。虽然这里的“值”元素实际上是字符串,但这并不是重点(正如我在本文末尾解释的那样)。重点是第一次就做好:

collection.find(
    {
        "ingredients": {
            "$elemMatch": {
                "name": "ingredient1",
                "value": { "$gt": 50 }
            }
         }
    },
    { "ingredients.$": 1 }
)

第二部分的$postional operator,它只从查询条件中投影出匹配的数组元素。

这也比 JavaScript 评估快得多,因为评估代码不需要编译并使用本机编码运算符,以及可以在“名称”甚至“”上使用“索引” 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 ] }
                    ]
                }
            }
        }
    }}
])

在这两种情况下,您都在有效地“过滤”在初始文档匹配后与条件不匹配的数组元素。


此外,由于您的“值”现在实际上是“字符串”,因此您确实应该将其更改为数字。这是一个基本过程:

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 ) 
        );
    });
})

这也不能像其他形式那样“投射”结果中的匹配值。

【讨论】:

    【解决方案2】:

    尝试使用 $elemMatch:

    var queryTest = collection.find(
       { ingredients: { $elemMatch: { name: "ingredient1", value: { $gte: 50 } } } }
    );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-16
      • 2020-08-04
      • 1970-01-01
      • 1970-01-01
      • 2021-12-31
      • 1970-01-01
      • 2021-09-03
      • 1970-01-01
      相关资源
      最近更新 更多