MongoDB Find key with max value within nested document

时间:2015-09-01 21:35:53

标签: mongodb mongodb-query

I have a document like this:

{
    timestamp: ISODate("2013-10-10T23:00:00.000Z"),
    values: {
        0: 25,
        1: 2, 
        3: 16,
        4: 12,
        5: 10
    }
}

Two questions:

  1. How can I get the "argmax" 0 from the nested document in values?
  2. If I have multiple documents like this, can I query for all documents with an "argmax" of 2, for instance?

1 个答案:

答案 0 :(得分:2)

你真的需要改变你构建文档的方式,因为你现在所拥有的并不好。像你这样的嵌套对象不能在正常的查询操作中“遍历”,因此它们无法有效地搜索“跨键”。

执行此操作的唯一方法是使用$where的JavaScript评估,这意味着可以使用“无索引”来优化搜索。它也基本上是对集合中每个文档的“强力”匹配:

db.collection.find(function() {
    var values = this.values;
    return Object.keys(values).some(function(key) {
       return values[key] == 2;
    });
})

这只是在嵌套键中找到值“2”,以便找出“最大”值是否为“2”,那么你会这样做:

db.collection.find(function() {
    var values = this.values;
    return Math.max.apply(null, Object.keys(values).map(function(key) {
       return values[key];
    })) == 2;
})

所以蛮力不好。最好用“值”作为“数组”来构建文档。然后所有本机查询都可以正常工作:

    {
        "timestamp": ISODate("2013-10-10T23:00:00.000Z"),
        "values": [25, 2, 16, 12, 10]
    }

现在你可以做到:

 db.collection.aggregate([
     { "$match": { "values": 2 } },
     { "$unwind": "$values" },
     { "$group": {
         "_id": "$_id",
         "timestamp": { "$first": "$timestamp" },
         "values": { "$push": "$values" },
         "maxVal": { "$max": "$values" }
     }},
     { "$match": { "maxVal": 2 } }
])

乍一看似乎更麻烦,但是使用本机运算符而不是JavaScript转换构建它确实会提高效率。它的效率也显着提高,因为现在可以实际搜索“values”数组是否实际上甚至包含“2”作为使用索引的值,而无需在循环代码中测试所有内容。

主要工作是在测试数组的“max”值时完成的,所以即使这不是最明亮的例子,你甚至可以看到普通查询操作如何与JavaScript评估相结合的明显区别现在,让这个过程更快:

db.collection.find({
    "values": 2,
    "$where": function() {
        return Math.max.apply(null,this.values) == 2;
    }
})

因此,初始"values": 2将立即过滤包含“2”的文档的文档,后续表达式仅过滤掉那些数组的“max”值为“2”的文档。

此外,如果您打算定期查询这样的“最大”值,那么最好将此值存储为文档本身的离散字段,如下所示:

    {
        "timestamp": ISODate("2013-10-10T23:00:00.000Z"),
        "values": [25, 2, 16, 12, 10],
        "minValue": 2,
        "maxValue": 25
    }

然后查找“最大值”为2的文档非常简单:

db.collection.find({ "maxValue": 2 })

或所有文件中最大的“最大”:

db.collection.find().sort({ "maxValue": -1 }).limit(1)

甚至同时来自所有文件的“min”和“max”:

db.collection.aggregate([
    { "$group": {
        "_id": null,
        "minValue": { "$min": "$minValue" },
        "maxValue": { "$max": "$maxValue" }
    }}
])

在添加新“值”时维护此数据,只需在更新时使用 $min $max 更新运算符文献。所以要在值中添加“26”:

db.collection.update(
    { "timestamp": ISODate("2013-10-10T23:00:00.000Z") },
    {
        "$push": { "values": 26 },
        "$min": { "minValue": 26 },
        "$max": { "maxValue": 26 }
    }
)

这导致只有$min$max分别小于或大于当前值的调整值。

    {
        "timestamp": ISODate("2013-10-10T23:00:00.000Z"),
        "values": [25, 2, 16, 12, 10, 26],
        "minValue": 2,
        "maxValue": 26
    }

因此,应该清楚地看到结构为什么重要,并且应该避免嵌套对象优先于您想要遍历数据的数组,分析文档本身,或者实际上跨多个文档在一个集合中。