MongoDB中最后n个子数组元素包含值的文档

时间:2014-04-13 18:56:12

标签: mongodb aggregation-framework

在MongoDB中考虑这组数据......

{
_id: 1,
name: "Johnny",
properties: [
    {
        type: "A",
        value: 257,
        date: "4/1/2014"
    },
    {
        type: "A",
        value: 200,
        date: "4/2/2014"
    },
    {
        type: "B",
        value: 301,
        date: "4/3/2014"
    },
    ...]
}

查询其中一个(或多个)最后两个"属性"的文档的正确方法是什么?元素的值> x,或者最后两个"属性中的一个(或多个)"类型" A"有一个值> X?

3 个答案:

答案 0 :(得分:1)

如果你可以修改你的插入方法,请尝试如下;

更改您的更新以推送以下内容:

doc = { type : "A", "value" : 123, "date" : new Date() }
db.foo.update( {_id:1}, { "$push" : { "properties" : { "$each" : [ doc ], "$sort" : { date : -1} } } } )  

这将为您提供按时间降序排序的文档数组,使得"最新的"首先是文件。

您现在可以使用标准的MongoDB点表示法来查询0, 1, etc elements of your properties array,它代表了最新的逻辑添加。

答案 1 :(得分:0)

您是否考虑过使用$ where子句?不是最有效的,但我认为它应该得到你想要的。例如,如果您希望每个具有最后两个属性元素值字段大于200的文档,您可以尝试:

db.collection.find({properties:{$exists:true},
                    $where: "(this.properties[this.properties.length-1].value > 200)||
                             (this.properties[this.properties.length-2].value > 200)"});

这需要对边缘情况(例如数组< 2成员)和更复杂的查询(通过“类型”字段)进行一些工作,但是应该让你开始。

答案 2 :(得分:0)

根据评论,聚合框架不仅仅是简单地“聚合”值,因此您可以利用各种pipeline operators来完成使用{{3}无法实现的非常高级的事情。 }}

db.collection.aggregate([
    // Match documents that "could" meet the conditions to narrow down
    { "$match": {
        "properties": { "$elemMatch": {
            "type": "A", "value": { "$gt": 200 }
        }}
    }},

    // Keep a copy of the document for later with an array copy
    { "$project": {
        "_id": {
            "_id": "$_id",
            "name": "$name",
            "properties": "$properties" 
        },
        "properties": 1
    }},

    // Unwind the array to "de-normalize"
    { "$unwind": "$properties" },

    // Get the "last" element of the array and copy the existing one
    { "$group": {
        "_id": "$_id",
        "properties": { "$last": "$_id.properties" },
        "last": { "$last": "$properties" },
        "count": { "$sum": 1 }
    }},

    // Unwind the copy again
    { "$unwind": "$properties" },

    // Project to mark the element you already have
    { "$project": {
          "properties": 1,
          "last": 1,
          "count": 1,
          "seen": { "$eq": [ "$properties", "$last" ] }
    }},

    // Match again, being careful to keep any array with one element only
    // This gets rid of the element you already kept
    { "$match": { 
        "$or": [
            { "seen": false },
            { "seen": true, "count": 1 }
        ]
    }},

    // Group to get the second last element as "next"
    { "$group": {
        "_id": "$_id",
        "last": { "$last": "$last" },
        "next": { "$last": "$properties" }
    }},

    // Then match to see if either of those elements fits
    { "$match": {
        "$or": [
            { "last.type": "A", "last.value": { "$gt": 200 } },
            { "next.type": "A", "next.value": { "$gt": 200 } }
        ]
    }},

    // Finally restore your matching documents
    { "$project": {
        "_id": "$_id._id",
        "name": "$_id.name",
        "properties": "$_id.properties"
    }}

])

详细介绍一下:

  • 第一个.find()用法是确保您只处理可能“可能”与扩展条件匹配的文档。像这样优化总是一个好主意。

  • 下一阶段是$match,因为您可能希望保留原始文档的详细信息,并且您至少需要再次使用该数组才能获得第二个元素。

  • 接下来的各个阶段使用$project来将数组分解为单个文档,然后是$unwind,后者用于查找文档中的最后一项{{1边界。这实际上是数组中的最后一项。另外,你要保留数组元素的数量。

  • 然后在原始数组内容上再次使用$group之后,$unwind的使用再次向文档添加“看到”字段,指示通过使用$ eq运算符是否或者原件中的文件实际上是以前作为“最后”元素的文件。

  • 在该阶段之后,您再次发出$project以便从结果中过滤掉最后一个文档,同时确保在不删除最初匹配数组长度的任何内容的情况下实际上是1。

  • 从这里你想再次$match以获得数组中的“倒数第二”元素(或者实际上只有一个元素的“最后”元素。

    < / LI>
  • 最后的步骤只是$group,其中最后两个元素中的任何一个符合条件,然后最终$match文档的原始形式。

因此,虽然这是相当复杂的,当然,通过在数组末尾测试的项目数量可以增加复杂性,并且可以显示聚合如何非常适合问题。

在可能的情况下,这是最好的方法,因为调用JavaScript解释器会传递与聚合使用的本机代码相比的开销。

使用mapReduce会删除使用最后两个可能元素(或更多)的代码复杂性,但它会自然地调用JavaScript解释器,因此运行速度会慢得多。


对于记录,由于问题中的示例是匹配,因此这里有一些数据将匹配最后两个文档,其中一个数据只有一个元素:< / p>

_id